format: apply repo prettier (3.8.1) to the folded skills tree

963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
This commit is contained in:
fargo
2026-08-19 14:37:17 -05:00
parent d2eeb64433
commit 1a822493ba
962 changed files with 29594 additions and 27188 deletions
+143 -143
View File
@@ -6,13 +6,13 @@ Complete agent skill fleet for Mosaic Stack. 95 skills across 12 domains — cod
All skills were reviewed on 2026-02-16. Findings: All skills were reviewed on 2026-02-16. Findings:
| ID | Severity | Skill | Issue | Action | | ID | Severity | Skill | Issue | Action |
|----|----------|-------|-------|--------| | ----- | ------------- | ---------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| C-001 | **CRITICAL** | `vercel-deploy` | Uploads entire project to external endpoint via `curl` | **REMOVED** | | C-001 | **CRITICAL** | `vercel-deploy` | Uploads entire project to external endpoint via `curl` | **REMOVED** |
| C-002 | **ANNOTATED** | `docx`, `pptx`, `xlsx` | LD_PRELOAD shim compiles C at runtime to hook `socket()` | Security warnings added — legitimate sandbox workaround, should never activate on Docker Swarm | | C-002 | **ANNOTATED** | `docx`, `pptx`, `xlsx` | LD_PRELOAD shim compiles C at runtime to hook `socket()` | Security warnings added — legitimate sandbox workaround, should never activate on Docker Swarm |
| W-001 | WARNING | `using-superpowers` | Forces aggressive auto-loading via `<EXTREMELY-IMPORTANT>` tags | Awareness only — review before enabling | | W-001 | WARNING | `using-superpowers` | Forces aggressive auto-loading via `<EXTREMELY-IMPORTANT>` tags | Awareness only — review before enabling |
| W-002 | WARNING | `mcp-builder` | Can connect to arbitrary MCP servers | Awareness only — review server URLs | | W-002 | WARNING | `mcp-builder` | Can connect to arbitrary MCP servers | Awareness only — review server URLs |
| W-003 | WARNING | `create-agent` | Uses `Function()` constructor (eval equivalent) | Awareness only — review generated code | | W-003 | WARNING | `create-agent` | Uses `Function()` constructor (eval equivalent) | Awareness only — review generated code |
88 of 93 audited skills passed all checks as clean instruction-only SKILL.md files. 88 of 93 audited skills passed all checks as clean instruction-only SKILL.md files.
@@ -20,179 +20,179 @@ All skills were reviewed on 2026-02-16. Findings:
### Code Quality & Review (6) ### Code Quality & Review (6)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | -------------------------------- | ------------------------------------------------------------------------------- | ------------------------------- |
| `lint` | Zero-tolerance linting — detect linter, fix ALL violations, never disable rules | Mosaic Stack | | `lint` | Zero-tolerance linting — detect linter, fix ALL violations, never disable rules | Mosaic Stack |
| `pr-reviewer` | Structured PR code review workflow (Gitea/GitHub) | Adapted from SpillwaveSolutions | | `pr-reviewer` | Structured PR code review workflow (Gitea/GitHub) | Adapted from SpillwaveSolutions |
| `code-review-excellence` | Code review methodology and checklists | awesome-skills | | `code-review-excellence` | Code review methodology and checklists | awesome-skills |
| `verification-before-completion` | Evidence-based completion claims | obra/superpowers | | `verification-before-completion` | Evidence-based completion claims | obra/superpowers |
| `receiving-code-review` | How to receive and respond to code reviews | obra/superpowers | | `receiving-code-review` | How to receive and respond to code reviews | obra/superpowers |
| `requesting-code-review` | How to request effective code reviews | obra/superpowers | | `requesting-code-review` | How to request effective code reviews | obra/superpowers |
### Frontend & UI (8) ### Frontend & UI (8)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ----------------------------- | ----------------------------------------------------- | ----------------------- |
| `next-best-practices` | Next.js 15+ — RSC, async, self-hosting, data patterns | vercel-labs/next-skills | | `next-best-practices` | Next.js 15+ — RSC, async, self-hosting, data patterns | vercel-labs/next-skills |
| `vercel-react-best-practices` | React/Next.js performance (57 rules) | vercel-labs | | `vercel-react-best-practices` | React/Next.js performance (57 rules) | vercel-labs |
| `vercel-composition-patterns` | React composition and component patterns | vercel-labs | | `vercel-composition-patterns` | React composition and component patterns | vercel-labs |
| `vercel-react-native-skills` | React Native development patterns | vercel-labs | | `vercel-react-native-skills` | React Native development patterns | vercel-labs |
| `shadcn-ui` | Component patterns — forms, dialogs, tables, charts | developer-kit | | `shadcn-ui` | Component patterns — forms, dialogs, tables, charts | developer-kit |
| `tailwind-design-system` | Tailwind CSS v4 design system patterns | wshobson | | `tailwind-design-system` | Tailwind CSS v4 design system patterns | wshobson |
| `ui-animation` | Motion design — performance, accessibility, easing | mblode | | `ui-animation` | Motion design — performance, accessibility, easing | mblode |
| `web-design-guidelines` | Web design principles and guidelines | vercel-labs | | `web-design-guidelines` | Web design principles and guidelines | vercel-labs |
### Backend & API (4) ### Backend & API (4)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | --------------------------------- | ------------------------------------------------- | -------- |
| `nestjs-best-practices` | NestJS — 40 rules, 10 categories, priority-ranked | kadajett | | `nestjs-best-practices` | NestJS — 40 rules, 10 categories, priority-ranked | kadajett |
| `fastapi` | FastAPI + Pydantic v2 + async SQLAlchemy 2.0 | jezweb | | `fastapi` | FastAPI + Pydantic v2 + async SQLAlchemy 2.0 | jezweb |
| `architecture-patterns` | Clean Architecture, Hexagonal, DDD | wshobson | | `architecture-patterns` | Clean Architecture, Hexagonal, DDD | wshobson |
| `python-performance-optimization` | Profiling, memory, parallelization | wshobson | | `python-performance-optimization` | Profiling, memory, parallelization | wshobson |
### Authentication (5) ### Authentication (5)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ------------------------------------------ | -------------------------------------------------- | ----------- |
| `better-auth-best-practices` | Better-Auth — Drizzle, sessions, plugins, security | better-auth | | `better-auth-best-practices` | Better-Auth — Drizzle, sessions, plugins, security | better-auth |
| `create-auth-skill` | Creating custom Better-Auth skills | better-auth | | `create-auth-skill` | Creating custom Better-Auth skills | better-auth |
| `email-and-password-best-practices` | Email/password auth patterns | better-auth | | `email-and-password-best-practices` | Email/password auth patterns | better-auth |
| `organization-best-practices` | Multi-org/team auth patterns | better-auth | | `organization-best-practices` | Multi-org/team auth patterns | better-auth |
| `two-factor-authentication-best-practices` | 2FA implementation patterns | better-auth | | `two-factor-authentication-best-practices` | 2FA implementation patterns | better-auth |
### AI & Agent Building (7) ### AI & Agent Building (7)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ----------------------------- | --------------------------------------------------- | ---------------- |
| `ai-sdk` | Vercel AI SDK — streaming, multi-provider, agents | vercel/ai | | `ai-sdk` | Vercel AI SDK — streaming, multi-provider, agents | vercel/ai |
| `create-agent` | Modular agent with OpenRouter multi-model access | openrouterteam | | `create-agent` | Modular agent with OpenRouter multi-model access | openrouterteam |
| `proactive-agent` | WAL Protocol, compaction recovery, self-improvement | halthelobster | | `proactive-agent` | WAL Protocol, compaction recovery, self-improvement | halthelobster |
| `dispatching-parallel-agents` | Launching and managing parallel subagents | obra/superpowers | | `dispatching-parallel-agents` | Launching and managing parallel subagents | obra/superpowers |
| `subagent-driven-development` | Development workflow using subagents | obra/superpowers | | `subagent-driven-development` | Development workflow using subagents | obra/superpowers |
| `executing-plans` | Executing multi-step implementation plans | obra/superpowers | | `executing-plans` | Executing multi-step implementation plans | obra/superpowers |
| `using-superpowers` | Overview of the superpowers skill system | obra/superpowers | | `using-superpowers` | Overview of the superpowers skill system | obra/superpowers |
### Development Workflow (6) ### Development Workflow (6)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | -------------------------------- | --------------------------------------- | ---------------- |
| `test-driven-development` | TDD Red-Green-Refactor discipline | obra/superpowers | | `test-driven-development` | TDD Red-Green-Refactor discipline | obra/superpowers |
| `systematic-debugging` | Structured debugging methodology | obra/superpowers | | `systematic-debugging` | Structured debugging methodology | obra/superpowers |
| `using-git-worktrees` | Git worktree patterns for parallel work | obra/superpowers | | `using-git-worktrees` | Git worktree patterns for parallel work | obra/superpowers |
| `finishing-a-development-branch` | Branch cleanup, squash, merge patterns | obra/superpowers | | `finishing-a-development-branch` | Branch cleanup, squash, merge patterns | obra/superpowers |
| `writing-plans` | Writing effective implementation plans | obra/superpowers | | `writing-plans` | Writing effective implementation plans | obra/superpowers |
| `brainstorming` | Structured brainstorming methodology | obra/superpowers | | `brainstorming` | Structured brainstorming methodology | obra/superpowers |
### Document Generation (6) ### Document Generation (6)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ----------------- | ---------------------------------- | ---------- |
| `pdf` | PDF document generation | anthropics | | `pdf` | PDF document generation | anthropics |
| `docx` | Word document generation | anthropics | | `docx` | Word document generation | anthropics |
| `pptx` | PowerPoint presentation generation | anthropics | | `pptx` | PowerPoint presentation generation | anthropics |
| `xlsx` | Excel spreadsheet generation | anthropics | | `xlsx` | Excel spreadsheet generation | anthropics |
| `doc-coauthoring` | Collaborative document writing | anthropics | | `doc-coauthoring` | Collaborative document writing | anthropics |
| `internal-comms` | Internal communications drafting | anthropics | | `internal-comms` | Internal communications drafting | anthropics |
### Design & Creative (7) ### Design & Creative (7)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ----------------------- | --------------------------------------- | ---------- |
| `brand-guidelines` | Brand identity enforcement | anthropics | | `brand-guidelines` | Brand identity enforcement | anthropics |
| `frontend-design` | Frontend design patterns and principles | anthropics | | `frontend-design` | Frontend design patterns and principles | anthropics |
| `canvas-design` | Canvas/visual design patterns | anthropics | | `canvas-design` | Canvas/visual design patterns | anthropics |
| `algorithmic-art` | Generative/algorithmic art creation | anthropics | | `algorithmic-art` | Generative/algorithmic art creation | anthropics |
| `theme-factory` | Theme generation and customization | anthropics | | `theme-factory` | Theme generation and customization | anthropics |
| `slack-gif-creator` | Animated GIF creation for Slack | anthropics | | `slack-gif-creator` | Animated GIF creation for Slack | anthropics |
| `web-artifacts-builder` | Self-contained HTML artifact building | anthropics | | `web-artifacts-builder` | Self-contained HTML artifact building | anthropics |
### Marketing & Business (25) ### Marketing & Business (25)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | --------------------------- | --------------------------------------------- | ------------- |
| `marketing-ideas` | 139 ideas across 14 categories | coreyhaines31 | | `marketing-ideas` | 139 ideas across 14 categories | coreyhaines31 |
| `pricing-strategy` | SaaS pricing — value metrics, tiers, research | coreyhaines31 | | `pricing-strategy` | SaaS pricing — value metrics, tiers, research | coreyhaines31 |
| `programmatic-seo` | SEO at scale — templates, playbooks | coreyhaines31 | | `programmatic-seo` | SEO at scale — templates, playbooks | coreyhaines31 |
| `competitor-alternatives` | Competitor comparison pages | coreyhaines31 | | `competitor-alternatives` | Competitor comparison pages | coreyhaines31 |
| `referral-program` | Referral & affiliate programs | coreyhaines31 | | `referral-program` | Referral & affiliate programs | coreyhaines31 |
| `seo-audit` | Comprehensive SEO audit methodology | coreyhaines31 | | `seo-audit` | Comprehensive SEO audit methodology | coreyhaines31 |
| `copywriting` | Marketing copywriting patterns | coreyhaines31 | | `copywriting` | Marketing copywriting patterns | coreyhaines31 |
| `copy-editing` | Copy editing and proofreading | coreyhaines31 | | `copy-editing` | Copy editing and proofreading | coreyhaines31 |
| `content-strategy` | Content strategy and planning | coreyhaines31 | | `content-strategy` | Content strategy and planning | coreyhaines31 |
| `social-content` | Social media content creation | coreyhaines31 | | `social-content` | Social media content creation | coreyhaines31 |
| `email-sequence` | Email sequence design and automation | coreyhaines31 | | `email-sequence` | Email sequence design and automation | coreyhaines31 |
| `launch-strategy` | Product launch planning | coreyhaines31 | | `launch-strategy` | Product launch planning | coreyhaines31 |
| `marketing-psychology` | Psychology-driven marketing | coreyhaines31 | | `marketing-psychology` | Psychology-driven marketing | coreyhaines31 |
| `product-marketing-context` | Product marketing positioning | coreyhaines31 | | `product-marketing-context` | Product marketing positioning | coreyhaines31 |
| `paid-ads` | Paid advertising campaigns | coreyhaines31 | | `paid-ads` | Paid advertising campaigns | coreyhaines31 |
| `schema-markup` | Schema.org structured data | coreyhaines31 | | `schema-markup` | Schema.org structured data | coreyhaines31 |
| `analytics-tracking` | Analytics setup and tracking | coreyhaines31 | | `analytics-tracking` | Analytics setup and tracking | coreyhaines31 |
| `ab-test-setup` | A/B testing methodology | coreyhaines31 | | `ab-test-setup` | A/B testing methodology | coreyhaines31 |
| `page-cro` | Landing page conversion optimization | coreyhaines31 | | `page-cro` | Landing page conversion optimization | coreyhaines31 |
| `form-cro` | Form conversion optimization | coreyhaines31 | | `form-cro` | Form conversion optimization | coreyhaines31 |
| `signup-flow-cro` | Signup flow conversion optimization | coreyhaines31 | | `signup-flow-cro` | Signup flow conversion optimization | coreyhaines31 |
| `onboarding-cro` | User onboarding optimization | coreyhaines31 | | `onboarding-cro` | User onboarding optimization | coreyhaines31 |
| `popup-cro` | Popup/modal conversion optimization | coreyhaines31 | | `popup-cro` | Popup/modal conversion optimization | coreyhaines31 |
| `paywall-upgrade-cro` | Paywall/upgrade conversion optimization | coreyhaines31 | | `paywall-upgrade-cro` | Paywall/upgrade conversion optimization | coreyhaines31 |
| `free-tool-strategy` | Free tool as marketing strategy | coreyhaines31 | | `free-tool-strategy` | Free tool as marketing strategy | coreyhaines31 |
### Vue/Vite Ecosystem (16) ### Vue/Vite Ecosystem (16)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ---------------------------- | ----------------------------------------- | ------ |
| `vue` | Vue.js development patterns | antfu | | `vue` | Vue.js development patterns | antfu |
| `vue-best-practices` | Vue.js best practices and conventions | antfu | | `vue-best-practices` | Vue.js best practices and conventions | antfu |
| `vue-router-best-practices` | Vue Router patterns and guards | antfu | | `vue-router-best-practices` | Vue Router patterns and guards | antfu |
| `vue-testing-best-practices` | Vue component testing patterns | antfu | | `vue-testing-best-practices` | Vue component testing patterns | antfu |
| `vueuse-functions` | VueUse composable function patterns | antfu | | `vueuse-functions` | VueUse composable function patterns | antfu |
| `nuxt` | Nuxt.js framework patterns | antfu | | `nuxt` | Nuxt.js framework patterns | antfu |
| `vite` | Vite build tool configuration and plugins | antfu | | `vite` | Vite build tool configuration and plugins | antfu |
| `vitest` | Vitest testing framework patterns | antfu | | `vitest` | Vitest testing framework patterns | antfu |
| `vitepress` | VitePress documentation site patterns | antfu | | `vitepress` | VitePress documentation site patterns | antfu |
| `slidev` | Slidev presentation framework | antfu | | `slidev` | Slidev presentation framework | antfu |
| `pnpm` | pnpm package manager patterns | antfu | | `pnpm` | pnpm package manager patterns | antfu |
| `turborepo` | Turborepo monorepo patterns | antfu | | `turborepo` | Turborepo monorepo patterns | antfu |
| `unocss` | UnoCSS atomic CSS engine | antfu | | `unocss` | UnoCSS atomic CSS engine | antfu |
| `tsdown` | tsdown TypeScript bundler | antfu | | `tsdown` | tsdown TypeScript bundler | antfu |
| `pinia` | Pinia state management | antfu | | `pinia` | Pinia state management | antfu |
| `antfu` | Anthony Fu's coding conventions | antfu | | `antfu` | Anthony Fu's coding conventions | antfu |
### Orchestration (1) ### Orchestration (1)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ----------- | ------------------------------------------------------------------------------------------ | ------------ |
| `kickstart` | Launch orchestrator for milestone/issue/task — auto-discovers context, bootstraps tracking | Mosaic Stack | | `kickstart` | Launch orchestrator for milestone/issue/task — auto-discovers context, bootstraps tracking | Mosaic Stack |
### Meta / Skill Authoring (4) ### Meta / Skill Authoring (4)
| Skill | Purpose | Origin | | Skill | Purpose | Origin |
|-------|---------|--------| | ---------------- | --------------------------------------------- | ---------------- |
| `writing-skills` | TDD-based skill authoring methodology | obra/superpowers | | `writing-skills` | TDD-based skill authoring methodology | obra/superpowers |
| `skill-creator` | Anthropic's skill creation guide | anthropics | | `skill-creator` | Anthropic's skill creation guide | anthropics |
| `mcp-builder` | Building MCP (Model Context Protocol) servers | anthropics | | `mcp-builder` | Building MCP (Model Context Protocol) servers | anthropics |
| `webapp-testing` | Web application testing patterns | anthropics | | `webapp-testing` | Web application testing patterns | anthropics |
## Source Repositories ## Source Repositories
| Repository | Skills | Domain Focus | | Repository | Skills | Domain Focus |
|-----------|--------|-------------| | --------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------- |
| [anthropics/skills](https://github.com/anthropics/skills) | 16 | Documents, design, MCP, testing | | [anthropics/skills](https://github.com/anthropics/skills) | 16 | Documents, design, MCP, testing |
| [obra/superpowers](https://github.com/obra/superpowers) | 14 | Agent workflows, TDD, code review, planning | | [obra/superpowers](https://github.com/obra/superpowers) | 14 | Agent workflows, TDD, code review, planning |
| [coreyhaines31/marketingskills](https://github.com/coreyhaines31/marketingskills) | 25 | Marketing, CRO, SEO, growth | | [coreyhaines31/marketingskills](https://github.com/coreyhaines31/marketingskills) | 25 | Marketing, CRO, SEO, growth |
| [antfu/skills](https://github.com/antfu/skills) | 16 | Vue, Vite, Vitest, pnpm, Nuxt | | [antfu/skills](https://github.com/antfu/skills) | 16 | Vue, Vite, Vitest, pnpm, Nuxt |
| [better-auth/skills](https://github.com/better-auth/skills) | 5 | Authentication patterns | | [better-auth/skills](https://github.com/better-auth/skills) | 5 | Authentication patterns |
| [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills) | 4 | React, design | | [vercel-labs/agent-skills](https://github.com/vercel-labs/agent-skills) | 4 | React, design |
| [vercel-labs/next-skills](https://github.com/vercel-labs/next-skills) | 1 | Next.js 15+ | | [vercel-labs/next-skills](https://github.com/vercel-labs/next-skills) | 1 | Next.js 15+ |
| [vercel/ai](https://github.com/vercel/ai) | 1 | AI SDK | | [vercel/ai](https://github.com/vercel/ai) | 1 | AI SDK |
| [halthelobster/proactive-agent](https://github.com/halthelobster/proactive-agent) | 1 | Agent architecture | | [halthelobster/proactive-agent](https://github.com/halthelobster/proactive-agent) | 1 | Agent architecture |
| [openrouterteam/agent-skills](https://github.com/openrouterteam/agent-skills) | 1 | Agent building | | [openrouterteam/agent-skills](https://github.com/openrouterteam/agent-skills) | 1 | Agent building |
| [kadajett/agent-nestjs-skills](https://github.com/kadajett/agent-nestjs-skills) | 1 | NestJS | | [kadajett/agent-nestjs-skills](https://github.com/kadajett/agent-nestjs-skills) | 1 | NestJS |
| [jezweb/claude-skills](https://github.com/jezweb/claude-skills) | 1 | FastAPI | | [jezweb/claude-skills](https://github.com/jezweb/claude-skills) | 1 | FastAPI |
| [wshobson/agents](https://github.com/wshobson/agents) | 3 | Architecture, Python, Tailwind | | [wshobson/agents](https://github.com/wshobson/agents) | 3 | Architecture, Python, Tailwind |
| [mblode/agent-skills](https://github.com/mblode/agent-skills) | 1 | UI animation | | [mblode/agent-skills](https://github.com/mblode/agent-skills) | 1 | UI animation |
| [giuseppe-trisciuoglio/developer-kit](https://github.com/giuseppe-trisciuoglio/developer-kit) | 1 | shadcn/ui | | [giuseppe-trisciuoglio/developer-kit](https://github.com/giuseppe-trisciuoglio/developer-kit) | 1 | shadcn/ui |
| Mosaic Stack (original) | 4 | PR review, code review, orchestration, linting | | Mosaic Stack (original) | 4 | PR review, code review, orchestration, linting |
## Installation ## Installation
@@ -24,20 +24,24 @@ Before designing a test, understand:
## Core Principles ## Core Principles
### 1. Start with a Hypothesis ### 1. Start with a Hypothesis
- Not just "let's see what happens" - Not just "let's see what happens"
- Specific prediction of outcome - Specific prediction of outcome
- Based on reasoning or data - Based on reasoning or data
### 2. Test One Thing ### 2. Test One Thing
- Single variable per test - Single variable per test
- Otherwise you don't know what worked - Otherwise you don't know what worked
### 3. Statistical Rigor ### 3. Statistical Rigor
- Pre-determine sample size - Pre-determine sample size
- Don't peek and stop early - Don't peek and stop early
- Commit to the methodology - Commit to the methodology
### 4. Measure What Matters ### 4. Measure What Matters
- Primary metric tied to business value - Primary metric tied to business value
- Secondary metrics for context - Secondary metrics for context
- Guardrail metrics to prevent harm - Guardrail metrics to prevent harm
@@ -66,12 +70,12 @@ We'll know this is true when [metrics].
## Test Types ## Test Types
| Type | Description | Traffic Needed | | Type | Description | Traffic Needed |
|------|-------------|----------------| | --------- | -------------------------------- | -------------- |
| A/B | Two versions, single change | Moderate | | A/B | Two versions, single change | Moderate |
| A/B/n | Multiple variants | Higher | | A/B/n | Multiple variants | Higher |
| MVT | Multiple changes in combinations | Very high | | MVT | Multiple changes in combinations | Very high |
| Split URL | Different URLs for variants | Moderate | | Split URL | Different URLs for variants | Moderate |
--- ---
@@ -79,14 +83,15 @@ We'll know this is true when [metrics].
### Quick Reference ### Quick Reference
| Baseline | 10% Lift | 20% Lift | 50% Lift | | Baseline | 10% Lift | 20% Lift | 50% Lift |
|----------|----------|----------|----------| | -------- | ------------ | ----------- | ------------ |
| 1% | 150k/variant | 39k/variant | 6k/variant | | 1% | 150k/variant | 39k/variant | 6k/variant |
| 3% | 47k/variant | 12k/variant | 2k/variant | | 3% | 47k/variant | 12k/variant | 2k/variant |
| 5% | 27k/variant | 7k/variant | 1.2k/variant | | 5% | 27k/variant | 7k/variant | 1.2k/variant |
| 10% | 12k/variant | 3k/variant | 550/variant | | 10% | 12k/variant | 3k/variant | 550/variant |
**Calculators:** **Calculators:**
- [Evan Miller's](https://www.evanmiller.org/ab-testing/sample-size.html) - [Evan Miller's](https://www.evanmiller.org/ab-testing/sample-size.html)
- [Optimizely's](https://www.optimizely.com/sample-size-calculator/) - [Optimizely's](https://www.optimizely.com/sample-size-calculator/)
@@ -97,19 +102,23 @@ We'll know this is true when [metrics].
## Metrics Selection ## Metrics Selection
### Primary Metric ### Primary Metric
- Single metric that matters most - Single metric that matters most
- Directly tied to hypothesis - Directly tied to hypothesis
- What you'll use to call the test - What you'll use to call the test
### Secondary Metrics ### Secondary Metrics
- Support primary metric interpretation - Support primary metric interpretation
- Explain why/how the change worked - Explain why/how the change worked
### Guardrail Metrics ### Guardrail Metrics
- Things that shouldn't get worse - Things that shouldn't get worse
- Stop test if significantly negative - Stop test if significantly negative
### Example: Pricing Page Test ### Example: Pricing Page Test
- **Primary**: Plan selection rate - **Primary**: Plan selection rate
- **Secondary**: Time on page, plan distribution - **Secondary**: Time on page, plan distribution
- **Guardrail**: Support tickets, refund rate - **Guardrail**: Support tickets, refund rate
@@ -120,14 +129,15 @@ We'll know this is true when [metrics].
### What to Vary ### What to Vary
| Category | Examples | | Category | Examples |
|----------|----------| | -------------- | ------------------------------------------------- |
| Headlines/Copy | Message angle, value prop, specificity, tone | | Headlines/Copy | Message angle, value prop, specificity, tone |
| Visual Design | Layout, color, images, hierarchy | | Visual Design | Layout, color, images, hierarchy |
| CTA | Button copy, size, placement, number | | CTA | Button copy, size, placement, number |
| Content | Information included, order, amount, social proof | | Content | Information included, order, amount, social proof |
### Best Practices ### Best Practices
- Single, meaningful change - Single, meaningful change
- Bold enough to make a difference - Bold enough to make a difference
- True to the hypothesis - True to the hypothesis
@@ -136,13 +146,14 @@ We'll know this is true when [metrics].
## Traffic Allocation ## Traffic Allocation
| Approach | Split | When to Use | | Approach | Split | When to Use |
|----------|-------|-------------| | ------------ | --------------------- | ------------------------- |
| Standard | 50/50 | Default for A/B | | Standard | 50/50 | Default for A/B |
| Conservative | 90/10, 80/20 | Limit risk of bad variant | | Conservative | 90/10, 80/20 | Limit risk of bad variant |
| Ramping | Start small, increase | Technical risk mitigation | | Ramping | Start small, increase | Technical risk mitigation |
**Considerations:** **Considerations:**
- Consistency: Users see same variant on return - Consistency: Users see same variant on return
- Balanced exposure across time of day/week - Balanced exposure across time of day/week
@@ -151,11 +162,13 @@ We'll know this is true when [metrics].
## Implementation ## Implementation
### Client-Side ### Client-Side
- JavaScript modifies page after load - JavaScript modifies page after load
- Quick to implement, can cause flicker - Quick to implement, can cause flicker
- Tools: PostHog, Optimizely, VWO - Tools: PostHog, Optimizely, VWO
### Server-Side ### Server-Side
- Variant determined before render - Variant determined before render
- No flicker, requires dev work - No flicker, requires dev work
- Tools: PostHog, LaunchDarkly, Split - Tools: PostHog, LaunchDarkly, Split
@@ -165,6 +178,7 @@ We'll know this is true when [metrics].
## Running the Test ## Running the Test
### Pre-Launch Checklist ### Pre-Launch Checklist
- [ ] Hypothesis documented - [ ] Hypothesis documented
- [ ] Primary metric defined - [ ] Primary metric defined
- [ ] Sample size calculated - [ ] Sample size calculated
@@ -175,16 +189,19 @@ We'll know this is true when [metrics].
### During the Test ### During the Test
**DO:** **DO:**
- Monitor for technical issues - Monitor for technical issues
- Check segment quality - Check segment quality
- Document external factors - Document external factors
**DON'T:** **DON'T:**
- Peek at results and stop early - Peek at results and stop early
- Make changes to variants - Make changes to variants
- Add traffic from new sources - Add traffic from new sources
### The Peeking Problem ### The Peeking Problem
Looking at results before reaching sample size and stopping early leads to false positives and wrong decisions. Pre-commit to sample size and trust the process. Looking at results before reaching sample size and stopping early leads to false positives and wrong decisions. Pre-commit to sample size and trust the process.
--- ---
@@ -192,6 +209,7 @@ Looking at results before reaching sample size and stopping early leads to false
## Analyzing Results ## Analyzing Results
### Statistical Significance ### Statistical Significance
- 95% confidence = p-value < 0.05 - 95% confidence = p-value < 0.05
- Means <5% chance result is random - Means <5% chance result is random
- Not a guarantee—just a threshold - Not a guarantee—just a threshold
@@ -207,18 +225,19 @@ Looking at results before reaching sample size and stopping early leads to false
### Interpreting Results ### Interpreting Results
| Result | Conclusion | | Result | Conclusion |
|--------|------------| | ------------------------- | -------------------------------- |
| Significant winner | Implement variant | | Significant winner | Implement variant |
| Significant loser | Keep control, learn why | | Significant loser | Keep control, learn why |
| No significant difference | Need more traffic or bolder test | | No significant difference | Need more traffic or bolder test |
| Mixed signals | Dig deeper, maybe segment | | Mixed signals | Dig deeper, maybe segment |
--- ---
## Documentation ## Documentation
Document every test with: Document every test with:
- Hypothesis - Hypothesis
- Variants (with screenshots) - Variants (with screenshots)
- Results (sample, metrics, significance) - Results (sample, metrics, significance)
@@ -231,16 +250,19 @@ Document every test with:
## Common Mistakes ## Common Mistakes
### Test Design ### Test Design
- Testing too small a change (undetectable) - Testing too small a change (undetectable)
- Testing too many things (can't isolate) - Testing too many things (can't isolate)
- No clear hypothesis - No clear hypothesis
### Execution ### Execution
- Stopping early - Stopping early
- Changing things mid-test - Changing things mid-test
- Not checking implementation - Not checking implementation
### Analysis ### Analysis
- Ignoring confidence intervals - Ignoring confidence intervals
- Cherry-picking segments - Cherry-picking segments
- Over-interpreting inconclusive results - Over-interpreting inconclusive results
@@ -16,6 +16,7 @@ Reference for calculating sample sizes and test duration.
**Baseline conversion rate**: If your page converts at 5%, that's your baseline. **Baseline conversion rate**: If your page converts at 5%, that's your baseline.
**MDE (Minimum Detectable Effect)**: The smallest improvement you care about detecting. Set this based on: **MDE (Minimum Detectable Effect)**: The smallest improvement you care about detecting. Set this based on:
- Business impact (is a 5% lift meaningful?) - Business impact (is a 5% lift meaningful?)
- Implementation cost (worth the effort?) - Implementation cost (worth the effort?)
- Realistic expectations (what have past tests shown?) - Realistic expectations (what have past tests shown?)
@@ -30,53 +31,53 @@ Reference for calculating sample sizes and test duration.
### Conversion Rate: 1% ### Conversion Rate: 1%
| Lift to Detect | Sample per Variant | Total Sample | | Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------| | --------------- | ------------------ | ------------ |
| 5% (1% → 1.05%) | 1,500,000 | 3,000,000 | | 5% (1% → 1.05%) | 1,500,000 | 3,000,000 |
| 10% (1% → 1.1%) | 380,000 | 760,000 | | 10% (1% → 1.1%) | 380,000 | 760,000 |
| 20% (1% → 1.2%) | 97,000 | 194,000 | | 20% (1% → 1.2%) | 97,000 | 194,000 |
| 50% (1% → 1.5%) | 16,000 | 32,000 | | 50% (1% → 1.5%) | 16,000 | 32,000 |
| 100% (1% → 2%) | 4,200 | 8,400 | | 100% (1% → 2%) | 4,200 | 8,400 |
### Conversion Rate: 3% ### Conversion Rate: 3%
| Lift to Detect | Sample per Variant | Total Sample | | Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------| | --------------- | ------------------ | ------------ |
| 5% (3% → 3.15%) | 480,000 | 960,000 | | 5% (3% → 3.15%) | 480,000 | 960,000 |
| 10% (3% → 3.3%) | 120,000 | 240,000 | | 10% (3% → 3.3%) | 120,000 | 240,000 |
| 20% (3% → 3.6%) | 31,000 | 62,000 | | 20% (3% → 3.6%) | 31,000 | 62,000 |
| 50% (3% → 4.5%) | 5,200 | 10,400 | | 50% (3% → 4.5%) | 5,200 | 10,400 |
| 100% (3% → 6%) | 1,400 | 2,800 | | 100% (3% → 6%) | 1,400 | 2,800 |
### Conversion Rate: 5% ### Conversion Rate: 5%
| Lift to Detect | Sample per Variant | Total Sample | | Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------| | --------------- | ------------------ | ------------ |
| 5% (5% → 5.25%) | 280,000 | 560,000 | | 5% (5% → 5.25%) | 280,000 | 560,000 |
| 10% (5% → 5.5%) | 72,000 | 144,000 | | 10% (5% → 5.5%) | 72,000 | 144,000 |
| 20% (5% → 6%) | 18,000 | 36,000 | | 20% (5% → 6%) | 18,000 | 36,000 |
| 50% (5% → 7.5%) | 3,100 | 6,200 | | 50% (5% → 7.5%) | 3,100 | 6,200 |
| 100% (5% → 10%) | 810 | 1,620 | | 100% (5% → 10%) | 810 | 1,620 |
### Conversion Rate: 10% ### Conversion Rate: 10%
| Lift to Detect | Sample per Variant | Total Sample | | Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------| | ---------------- | ------------------ | ------------ |
| 5% (10% → 10.5%) | 130,000 | 260,000 | | 5% (10% → 10.5%) | 130,000 | 260,000 |
| 10% (10% → 11%) | 34,000 | 68,000 | | 10% (10% → 11%) | 34,000 | 68,000 |
| 20% (10% → 12%) | 8,700 | 17,400 | | 20% (10% → 12%) | 8,700 | 17,400 |
| 50% (10% → 15%) | 1,500 | 3,000 | | 50% (10% → 15%) | 1,500 | 3,000 |
| 100% (10% → 20%) | 400 | 800 | | 100% (10% → 20%) | 400 | 800 |
### Conversion Rate: 20% ### Conversion Rate: 20%
| Lift to Detect | Sample per Variant | Total Sample | | Lift to Detect | Sample per Variant | Total Sample |
|----------------|-------------------|--------------| | ---------------- | ------------------ | ------------ |
| 5% (20% → 21%) | 60,000 | 120,000 | | 5% (20% → 21%) | 60,000 | 120,000 |
| 10% (20% → 22%) | 16,000 | 32,000 | | 10% (20% → 22%) | 16,000 | 32,000 |
| 20% (20% → 24%) | 4,000 | 8,000 | | 20% (20% → 24%) | 4,000 | 8,000 |
| 50% (20% → 30%) | 700 | 1,400 | | 50% (20% → 30%) | 700 | 1,400 |
| 100% (20% → 40%) | 200 | 400 | | 100% (20% → 40%) | 200 | 400 |
--- ---
@@ -91,18 +92,21 @@ Duration (days) = (Sample per variant × Number of variants) / (Daily traffic ×
### Examples ### Examples
**Scenario 1: High-traffic page** **Scenario 1: High-traffic page**
- Need: 10,000 per variant (2 variants = 20,000 total) - Need: 10,000 per variant (2 variants = 20,000 total)
- Daily traffic: 5,000 visitors - Daily traffic: 5,000 visitors
- 100% exposed to test - 100% exposed to test
- Duration: 20,000 / 5,000 = **4 days** - Duration: 20,000 / 5,000 = **4 days**
**Scenario 2: Medium-traffic page** **Scenario 2: Medium-traffic page**
- Need: 30,000 per variant (60,000 total) - Need: 30,000 per variant (60,000 total)
- Daily traffic: 2,000 visitors - Daily traffic: 2,000 visitors
- 100% exposed - 100% exposed
- Duration: 60,000 / 2,000 = **30 days** - Duration: 60,000 / 2,000 = **30 days**
**Scenario 3: Low-traffic with partial exposure** **Scenario 3: Low-traffic with partial exposure**
- Need: 15,000 per variant (30,000 total) - Need: 15,000 per variant (30,000 total)
- Daily traffic: 500 visitors - Daily traffic: 500 visitors
- 50% exposed to test - 50% exposed to test
@@ -112,6 +116,7 @@ Duration (days) = (Sample per variant × Number of variants) / (Daily traffic ×
### Minimum Duration Rules ### Minimum Duration Rules
Even with sufficient sample size, run tests for at least: Even with sufficient sample size, run tests for at least:
- **1 full week**: To capture day-of-week variation - **1 full week**: To capture day-of-week variation
- **2 business cycles**: If B2B (weekday vs. weekend patterns) - **2 business cycles**: If B2B (weekday vs. weekend patterns)
- **Through paydays**: If e-commerce (beginning/end of month) - **Through paydays**: If e-commerce (beginning/end of month)
@@ -119,6 +124,7 @@ Even with sufficient sample size, run tests for at least:
### Maximum Duration Guidelines ### Maximum Duration Guidelines
Avoid running tests longer than 4-8 weeks: Avoid running tests longer than 4-8 weeks:
- Novelty effects wear off - Novelty effects wear off
- External factors intervene - External factors intervene
- Opportunity cost of other tests - Opportunity cost of other tests
@@ -131,21 +137,25 @@ Avoid running tests longer than 4-8 weeks:
**Evan Miller's Calculator** **Evan Miller's Calculator**
https://www.evanmiller.org/ab-testing/sample-size.html https://www.evanmiller.org/ab-testing/sample-size.html
- Simple interface - Simple interface
- Bookmark-worthy - Bookmark-worthy
**Optimizely's Calculator** **Optimizely's Calculator**
https://www.optimizely.com/sample-size-calculator/ https://www.optimizely.com/sample-size-calculator/
- Business-friendly language - Business-friendly language
- Duration estimates - Duration estimates
**AB Test Guide Calculator** **AB Test Guide Calculator**
https://www.abtestguide.com/calc/ https://www.abtestguide.com/calc/
- Includes Bayesian option - Includes Bayesian option
- Multiple test types - Multiple test types
**VWO Duration Calculator** **VWO Duration Calculator**
https://vwo.com/tools/ab-test-duration-calculator/ https://vwo.com/tools/ab-test-duration-calculator/
- Duration-focused - Duration-focused
- Good for planning - Good for planning
@@ -155,14 +165,15 @@ https://vwo.com/tools/ab-test-duration-calculator/
With more than 2 variants (A/B/n tests), you need more sample: With more than 2 variants (A/B/n tests), you need more sample:
| Variants | Multiplier | | Variants | Multiplier |
|----------|------------| | ----------- | -------------------------- |
| 2 (A/B) | 1x | | 2 (A/B) | 1x |
| 3 (A/B/C) | ~1.5x | | 3 (A/B/C) | ~1.5x |
| 4 (A/B/C/D) | ~2x | | 4 (A/B/C/D) | ~2x |
| 5+ | Consider reducing variants | | 5+ | Consider reducing variants |
**Why?** More comparisons increase chance of false positives. You're comparing: **Why?** More comparisons increase chance of false positives. You're comparing:
- A vs B - A vs B
- A vs C - A vs C
- B vs C (sometimes) - B vs C (sometimes)
@@ -174,22 +185,27 @@ Apply Bonferroni correction or use tools that handle this automatically.
## Common Sample Size Mistakes ## Common Sample Size Mistakes
### 1. Underpowered tests ### 1. Underpowered tests
**Problem**: Not enough sample to detect realistic effects **Problem**: Not enough sample to detect realistic effects
**Fix**: Be realistic about MDE, get more traffic, or don't test **Fix**: Be realistic about MDE, get more traffic, or don't test
### 2. Overpowered tests ### 2. Overpowered tests
**Problem**: Waiting for sample size when you already have significance **Problem**: Waiting for sample size when you already have significance
**Fix**: This is actually fine—you committed to sample size, honor it **Fix**: This is actually fine—you committed to sample size, honor it
### 3. Wrong baseline rate ### 3. Wrong baseline rate
**Problem**: Using wrong conversion rate for calculation **Problem**: Using wrong conversion rate for calculation
**Fix**: Use the specific metric and page, not site-wide averages **Fix**: Use the specific metric and page, not site-wide averages
### 4. Ignoring segments ### 4. Ignoring segments
**Problem**: Calculating for full traffic, then analyzing segments **Problem**: Calculating for full traffic, then analyzing segments
**Fix**: If you plan segment analysis, calculate sample for smallest segment **Fix**: If you plan segment analysis, calculate sample for smallest segment
### 5. Testing too many things ### 5. Testing too many things
**Problem**: Dividing traffic too many ways **Problem**: Dividing traffic too many ways
**Fix**: Prioritize ruthlessly, run fewer concurrent tests **Fix**: Prioritize ruthlessly, run fewer concurrent tests
@@ -214,19 +230,23 @@ Options when you can't get enough traffic:
If you must check results before reaching sample size: If you must check results before reaching sample size:
### What is it? ### What is it?
Statistical method that adjusts for multiple looks at data. Statistical method that adjusts for multiple looks at data.
### When to use ### When to use
- High-risk changes - High-risk changes
- Need to stop bad variants early - Need to stop bad variants early
- Time-sensitive decisions - Time-sensitive decisions
### Tools that support it ### Tools that support it
- Optimizely (Stats Accelerator) - Optimizely (Stats Accelerator)
- VWO (SmartStats) - VWO (SmartStats)
- PostHog (Bayesian approach) - PostHog (Bayesian approach)
### Tradeoff ### Tradeoff
- More flexibility to stop early - More flexibility to stop early
- Slightly larger sample size requirement - Slightly larger sample size requirement
- More complex analysis - More complex analysis
@@ -8,6 +8,7 @@ Templates for planning, documenting, and analyzing experiments.
# A/B Test: [Name] # A/B Test: [Name]
## Overview ## Overview
- **Owner**: [Name] - **Owner**: [Name]
- **Test ID**: [ID in testing tool] - **Test ID**: [ID in testing tool]
- **Page/Feature**: [What's being tested] - **Page/Feature**: [What's being tested]
@@ -23,24 +24,28 @@ We'll know this is true when [metrics].
## Test Design ## Test Design
| Element | Details | | Element | Details |
|---------|---------| | ------------------ | ------------------------- |
| Test type | A/B / A/B/n / MVT | | Test type | A/B / A/B/n / MVT |
| Duration | X weeks | | Duration | X weeks |
| Sample size | X per variant | | Sample size | X per variant |
| Traffic allocation | 50/50 | | Traffic allocation | 50/50 |
| Tool | [Tool name] | | Tool | [Tool name] |
| Implementation | Client-side / Server-side | | Implementation | Client-side / Server-side |
## Variants ## Variants
### Control (A) ### Control (A)
[Screenshot] [Screenshot]
- Current experience - Current experience
- [Key details about current state] - [Key details about current state]
### Variant (B) ### Variant (B)
[Screenshot or mockup] [Screenshot or mockup]
- [Specific change #1] - [Specific change #1]
- [Specific change #2] - [Specific change #2]
- Rationale: [Why we think this will win] - Rationale: [Why we think this will win]
@@ -48,32 +53,38 @@ We'll know this is true when [metrics].
## Metrics ## Metrics
### Primary ### Primary
- **Metric**: [metric name] - **Metric**: [metric name]
- **Definition**: [how it's calculated] - **Definition**: [how it's calculated]
- **Current baseline**: [X%] - **Current baseline**: [X%]
- **Minimum detectable effect**: [X%] - **Minimum detectable effect**: [X%]
### Secondary ### Secondary
- [Metric 1]: [what it tells us] - [Metric 1]: [what it tells us]
- [Metric 2]: [what it tells us] - [Metric 2]: [what it tells us]
- [Metric 3]: [what it tells us] - [Metric 3]: [what it tells us]
### Guardrails ### Guardrails
- [Metric that shouldn't get worse] - [Metric that shouldn't get worse]
- [Another safety metric] - [Another safety metric]
## Segment Analysis Plan ## Segment Analysis Plan
- Mobile vs. desktop - Mobile vs. desktop
- New vs. returning visitors - New vs. returning visitors
- Traffic source - Traffic source
- [Other relevant segments] - [Other relevant segments]
## Success Criteria ## Success Criteria
- Winner: [Primary metric improves by X% with 95% confidence] - Winner: [Primary metric improves by X% with 95% confidence]
- Loser: [Primary metric decreases significantly] - Loser: [Primary metric decreases significantly]
- Inconclusive: [What we'll do if no significant result] - Inconclusive: [What we'll do if no significant result]
## Pre-Launch Checklist ## Pre-Launch Checklist
- [ ] Hypothesis documented and reviewed - [ ] Hypothesis documented and reviewed
- [ ] Primary metric defined and trackable - [ ] Primary metric defined and trackable
- [ ] Sample size calculated - [ ] Sample size calculated
@@ -93,46 +104,50 @@ We'll know this is true when [metrics].
# A/B Test Results: [Name] # A/B Test Results: [Name]
## Summary ## Summary
| Element | Value |
|---------|-------| | Element | Value |
| Test ID | [ID] | | -------- | ----------------------------- |
| Dates | [Start] - [End] | | Test ID | [ID] |
| Duration | X days | | Dates | [Start] - [End] |
| Result | Winner / Loser / Inconclusive | | Duration | X days |
| Decision | [What we're doing] | | Result | Winner / Loser / Inconclusive |
| Decision | [What we're doing] |
## Hypothesis (Reminder) ## Hypothesis (Reminder)
[Copy from test plan] [Copy from test plan]
## Results ## Results
### Sample Size ### Sample Size
| Variant | Target | Actual | % of target | | Variant | Target | Actual | % of target |
|---------|--------|--------|-------------| | ------- | ------ | ------ | ----------- |
| Control | X | Y | Z% | | Control | X | Y | Z% |
| Variant | X | Y | Z% | | Variant | X | Y | Z% |
### Primary Metric: [Metric Name] ### Primary Metric: [Metric Name]
| Variant | Value | 95% CI | vs. Control |
|---------|-------|--------|-------------| | Variant | Value | 95% CI | vs. Control |
| Control | X% | [X%, Y%] | — | | ------- | ----- | -------- | ----------- |
| Variant | X% | [X%, Y%] | +X% | | Control | X% | [X%, Y%] | |
| Variant | X% | [X%, Y%] | +X% |
**Statistical significance**: p = X.XX (95% = sig / not sig) **Statistical significance**: p = X.XX (95% = sig / not sig)
**Practical significance**: [Is this lift meaningful for the business?] **Practical significance**: [Is this lift meaningful for the business?]
### Secondary Metrics ### Secondary Metrics
| Metric | Control | Variant | Change | Significant? | | Metric | Control | Variant | Change | Significant? |
|--------|---------|---------|--------|--------------| | ---------- | ------- | ------- | ------ | ------------ |
| [Metric 1] | X | Y | +Z% | Yes/No | | [Metric 1] | X | Y | +Z% | Yes/No |
| [Metric 2] | X | Y | +Z% | Yes/No | | [Metric 2] | X | Y | +Z% | Yes/No |
### Guardrail Metrics ### Guardrail Metrics
| Metric | Control | Variant | Change | Concern? | | Metric | Control | Variant | Change | Concern? |
|--------|---------|---------|--------|----------| | ---------- | ------- | ------- | ------ | -------- |
| [Metric 1] | X | Y | +Z% | Yes/No | | [Metric 1] | X | Y | +Z% | Yes/No |
### Segment Analysis ### Segment Analysis
@@ -151,12 +166,15 @@ We'll know this is true when [metrics].
## Interpretation ## Interpretation
### What happened? ### What happened?
[Explanation of results in plain language] [Explanation of results in plain language]
### Why do we think this happened? ### Why do we think this happened?
[Analysis and reasoning] [Analysis and reasoning]
### Caveats ### Caveats
[Any limitations, external factors, or concerns] [Any limitations, external factors, or concerns]
## Decision ## Decision
@@ -170,14 +188,17 @@ We'll know this is true when [metrics].
## Learnings ## Learnings
### What we learned ### What we learned
- [Key insight 1] - [Key insight 1]
- [Key insight 2] - [Key insight 2]
### What to test next ### What to test next
- [Follow-up test idea 1] - [Follow-up test idea 1]
- [Follow-up test idea 2] - [Follow-up test idea 2]
### Impact ### Impact
- **Projected lift**: [X% improvement in Y metric] - **Projected lift**: [X% improvement in Y metric]
- **Business impact**: [Revenue, conversions, etc.] - **Business impact**: [Revenue, conversions, etc.]
``` ```
@@ -189,11 +210,11 @@ We'll know this is true when [metrics].
For tracking all tests in a central location: For tracking all tests in a central location:
```markdown ```markdown
| Test ID | Name | Page | Dates | Primary Metric | Result | Lift | Link | | Test ID | Name | Page | Dates | Primary Metric | Result | Lift | Link |
|---------|------|------|-------|----------------|--------|------|------| | ------- | -------------------- | -------- | --------- | -------------- | ------------ | ---- | ------ |
| 001 | Hero headline test | Homepage | 1/1-1/15 | CTR | Winner | +12% | [Link] | | 001 | Hero headline test | Homepage | 1/1-1/15 | CTR | Winner | +12% | [Link] |
| 002 | Pricing table layout | Pricing | 1/10-1/31 | Plan selection | Loser | -5% | [Link] | | 002 | Pricing table layout | Pricing | 1/10-1/31 | Plan selection | Loser | -5% | [Link] |
| 003 | Signup form fields | Signup | 2/1-2/14 | Completion | Inconclusive | +2% | [Link] | | 003 | Signup form fields | Signup | 2/1-2/14 | Completion | Inconclusive | +2% | [Link] |
``` ```
--- ---
@@ -225,15 +246,18 @@ For simple tests that don't need full documentation:
**Current sample**: X% of target **Current sample**: X% of target
### Preliminary observations ### Preliminary observations
[What we're seeing - without making decisions yet] [What we're seeing - without making decisions yet]
### Next steps ### Next steps
[What happens next] [What happens next]
### Timeline ### Timeline
- [Date]: Analysis complete - [Date]: Analysis complete
- [Date]: Decision and recommendation - [Date]: Decision and recommendation
- [Date]: Implementation (if winner) - [Date]: Implementation 'if winner'
``` ```
--- ---
@@ -242,14 +266,14 @@ For simple tests that don't need full documentation:
For deciding which tests to run: For deciding which tests to run:
| Factor | Weight | Test A | Test B | Test C | | Factor | Weight | Test A | Test B | Test C |
|--------|--------|--------|--------|--------| | ------------------------ | ------ | ------ | ------ | ------ |
| Potential impact | 30% | | | | | Potential impact | 30% | | | |
| Confidence in hypothesis | 25% | | | | | Confidence in hypothesis | 25% | | | |
| Ease of implementation | 20% | | | | | Ease of implementation | 20% | | | |
| Risk if wrong | 15% | | | | | Risk if wrong | 15% | | | |
| Strategic alignment | 10% | | | | | Strategic alignment | 10% | | | |
| **Total** | | | | | | **Total** | | | | |
Scoring: 1-5 (5 = best) Scoring: 1-5 (5 = best)
@@ -260,9 +284,9 @@ Scoring: 1-5 (5 = best)
For collecting test ideas: For collecting test ideas:
```markdown ```markdown
| ID | Page/Area | Observation | Hypothesis | Potential Impact | Status | | ID | Page/Area | Observation | Hypothesis | Potential Impact | Status |
|----|-----------|-------------|------------|------------------|--------| | --- | --------- | ------------------- | ------------------------------------- | ---------------- | ------- |
| H1 | Homepage | Low scroll depth | Shorter hero will increase scroll | High | Testing | | H1 | Homepage | Low scroll depth | Shorter hero will increase scroll | High | Testing |
| H2 | Pricing | Users compare plans | Comparison table will help | Medium | Backlog | | H2 | Pricing | Users compare plans | Comparison table will help | Medium | Backlog |
| H3 | Signup | Drop-off at email | Social login will increase completion | Medium | Backlog | | H3 | Signup | Drop-off at email | Social login will increase completion | Medium | Backlog |
``` ```
@@ -225,7 +225,7 @@ export default function Page() {
transport: new DefaultChatTransport({ api: '/api/chat' }), transport: new DefaultChatTransport({ api: '/api/chat' }),
}); });
const handleSubmit = e => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
sendMessage({ text: input }); sendMessage({ text: input });
setInput(''); setInput('');
@@ -233,7 +233,7 @@ export default function Page() {
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<input value={input} onChange={e => setInput(e.target.value)} /> <input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit">Send</button> <button type="submit">Send</button>
</form> </form>
); );
@@ -259,18 +259,14 @@ Typed tool parts also use different property names:
case 'text': case 'text':
return <div key={`${message.id}-${i}`}>{part.text}</div>; return <div key={`${message.id}-${i}`}>{part.text}</div>;
case 'tool-invocation': // deprecated: use typed tool parts instead case 'tool-invocation': // deprecated: use typed tool parts instead
return ( return <pre key={`${message.id}-${i}`}>{JSON.stringify(part.toolInvocation, null, 2)}</pre>;
<pre key={`${message.id}-${i}`}>
{JSON.stringify(part.toolInvocation, null, 2)}
</pre>
);
} }
}); });
} }
// ✅ Correct - using typed tool parts (recommended) // ✅ Correct - using typed tool parts (recommended)
{ {
message.parts.map(part => { message.parts.map((part) => {
switch (part.type) { switch (part.type) {
case 'text': case 'text':
return part.text; return part.text;
@@ -288,7 +284,7 @@ Typed tool parts also use different property names:
import { isToolUIPart } from 'ai'; import { isToolUIPart } from 'ai';
{ {
message.parts.map(part => { message.parts.map((part) => {
if (part.type === 'text') { if (part.type === 'text') {
return part.text; return part.text;
} }
@@ -85,7 +85,7 @@ export function Chat() {
return ( return (
<div> <div>
{messages.map(message => ( {messages.map((message) => (
<Message key={message.id} message={message} /> <Message key={message.id} message={message} />
))} ))}
</div> </div>
@@ -161,11 +161,7 @@ Then import only the type in your component:
// components/weather-tool.tsx // components/weather-tool.tsx
import type { WeatherToolInvocation } from '@/lib/tools/weather-tool'; import type { WeatherToolInvocation } from '@/lib/tools/weather-tool';
export function WeatherToolComponent({ export function WeatherToolComponent({ invocation }: { invocation: WeatherToolInvocation }) {
invocation,
}: {
invocation: WeatherToolInvocation;
}) {
// invocation.input and invocation.output are fully typed // invocation.input and invocation.output are fully typed
if (invocation.state === 'output-available') { if (invocation.state === 'output-available') {
return ( return (
@@ -7,6 +7,7 @@ license: Complete terms in LICENSE.txt
Algorithmic philosophies are computational aesthetic movements that are then expressed through code. Output .md files (philosophy), .html files (interactive viewer), and .js files (generative algorithms). Algorithmic philosophies are computational aesthetic movements that are then expressed through code. Output .md files (philosophy), .html files (interactive viewer), and .js files (generative algorithms).
This happens in two steps: This happens in two steps:
1. Algorithmic Philosophy Creation (.md file) 1. Algorithmic Philosophy Creation (.md file)
2. Express by creating p5.js generative art (.html + .js files) 2. Express by creating p5.js generative art (.html + .js files)
@@ -15,17 +16,20 @@ First, undertake this task:
## ALGORITHMIC PHILOSOPHY CREATION ## ALGORITHMIC PHILOSOPHY CREATION
To begin, create an ALGORITHMIC PHILOSOPHY (not static images or templates) that will be interpreted through: To begin, create an ALGORITHMIC PHILOSOPHY (not static images or templates) that will be interpreted through:
- Computational processes, emergent behavior, mathematical beauty - Computational processes, emergent behavior, mathematical beauty
- Seeded randomness, noise fields, organic systems - Seeded randomness, noise fields, organic systems
- Particles, flows, fields, forces - Particles, flows, fields, forces
- Parametric variation and controlled chaos - Parametric variation and controlled chaos
### THE CRITICAL UNDERSTANDING ### THE CRITICAL UNDERSTANDING
- What is received: Some subtle input or instructions by the user to take into account, but use as a foundation; it should not constrain creative freedom. - What is received: Some subtle input or instructions by the user to take into account, but use as a foundation; it should not constrain creative freedom.
- What is created: An algorithmic philosophy/generative aesthetic movement. - What is created: An algorithmic philosophy/generative aesthetic movement.
- What happens next: The same version receives the philosophy and EXPRESSES IT IN CODE - creating p5.js sketches that are 90% algorithmic generation, 10% essential parameters. - What happens next: The same version receives the philosophy and EXPRESSES IT IN CODE - creating p5.js sketches that are 90% algorithmic generation, 10% essential parameters.
Consider this approach: Consider this approach:
- Write a manifesto for a generative art movement - Write a manifesto for a generative art movement
- The next phase involves writing the algorithm that brings it to life - The next phase involves writing the algorithm that brings it to life
@@ -38,6 +42,7 @@ The philosophy must emphasize: Algorithmic expression. Emergent behavior. Comput
**Articulate the philosophy** (4-6 paragraphs - concise but complete): **Articulate the philosophy** (4-6 paragraphs - concise but complete):
To capture the ALGORITHMIC essence, express how this philosophy manifests through: To capture the ALGORITHMIC essence, express how this philosophy manifests through:
- Computational processes and mathematical relationships? - Computational processes and mathematical relationships?
- Noise functions and randomness patterns? - Noise functions and randomness patterns?
- Particle behaviors and field dynamics? - Particle behaviors and field dynamics?
@@ -45,6 +50,7 @@ To capture the ALGORITHMIC essence, express how this philosophy manifests throug
- Parametric variation and emergent complexity? - Parametric variation and emergent complexity?
**CRITICAL GUIDELINES:** **CRITICAL GUIDELINES:**
- **Avoid redundancy**: Each algorithmic aspect should be mentioned once. Avoid repeating concepts about noise theory, particle dynamics, or mathematical principles unless adding new depth. - **Avoid redundancy**: Each algorithmic aspect should be mentioned once. Avoid repeating concepts about noise theory, particle dynamics, or mathematical principles unless adding new depth.
- **Emphasize craftsmanship REPEATEDLY**: The philosophy MUST stress multiple times that the final algorithm should appear as though it took countless hours to develop, was refined with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted algorithm," "the product of deep computational expertise," "painstaking optimization," "master-level implementation." - **Emphasize craftsmanship REPEATEDLY**: The philosophy MUST stress multiple times that the final algorithm should appear as though it took countless hours to develop, was refined with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted algorithm," "the product of deep computational expertise," "painstaking optimization," "master-level implementation."
- **Leave creative space**: Be specific about the algorithmic direction, but concise enough that the next Claude has room to make interpretive implementation choices at an extremely high level of craftsmanship. - **Leave creative space**: Be specific about the algorithmic direction, but concise enough that the next Claude has room to make interpretive implementation choices at an extremely high level of craftsmanship.
@@ -73,9 +79,10 @@ Algorithmic expression: Vector fields constructed from mathematical functions or
Philosophy: Random processes crystallizing into ordered structures. Philosophy: Random processes crystallizing into ordered structures.
Algorithmic expression: Randomized circle packing or Voronoi tessellation. Start with random points, let them evolve through relaxation algorithms. Cells push apart until equilibrium. Color based on cell size, neighbor count, or distance from center. The organic tiling that emerges feels both random and inevitable. Every seed produces unique crystalline beauty - the mark of a master-level generative algorithm. Algorithmic expression: Randomized circle packing or Voronoi tessellation. Start with random points, let them evolve through relaxation algorithms. Cells push apart until equilibrium. Color based on cell size, neighbor count, or distance from center. The organic tiling that emerges feels both random and inevitable. Every seed produces unique crystalline beauty - the mark of a master-level generative algorithm.
*These are condensed examples. The actual algorithmic philosophy should be 4-6 substantial paragraphs.* _These are condensed examples. The actual algorithmic philosophy should be 4-6 substantial paragraphs._
### ESSENTIAL PRINCIPLES ### ESSENTIAL PRINCIPLES
- **ALGORITHMIC PHILOSOPHY**: Creating a computational worldview to be expressed through code - **ALGORITHMIC PHILOSOPHY**: Creating a computational worldview to be expressed through code
- **PROCESS OVER PRODUCT**: Always emphasize that beauty emerges from the algorithm's execution - each run is unique - **PROCESS OVER PRODUCT**: Always emphasize that beauty emerges from the algorithm's execution - each run is unique
- **PARAMETRIC EXPRESSION**: Ideas communicate through mathematical relationships, forces, behaviors - not static composition - **PARAMETRIC EXPRESSION**: Ideas communicate through mathematical relationships, forces, behaviors - not static composition
@@ -113,12 +120,14 @@ With the philosophy AND conceptual framework established, express it through cod
5. **Replace only the VARIABLE sections** marked in the file's comments (algorithm, parameters, UI controls for parameters) 5. **Replace only the VARIABLE sections** marked in the file's comments (algorithm, parameters, UI controls for parameters)
**Avoid:** **Avoid:**
- ❌ Creating HTML from scratch - ❌ Creating HTML from scratch
- ❌ Inventing custom styling or color schemes - ❌ Inventing custom styling or color schemes
- ❌ Using system fonts or dark themes - ❌ Using system fonts or dark themes
- ❌ Changing the sidebar structure - ❌ Changing the sidebar structure
**Follow these practices:** **Follow these practices:**
- ✅ Copy the template's exact HTML structure - ✅ Copy the template's exact HTML structure
- ✅ Keep Anthropic branding (Poppins/Lora fonts, light colors, gradient backdrop) - ✅ Keep Anthropic branding (Poppins/Lora fonts, light colors, gradient backdrop)
- ✅ Maintain the sidebar layout (Seed → Parameters → Colors? → Actions) - ✅ Maintain the sidebar layout (Seed → Parameters → Colors? → Actions)
@@ -133,6 +142,7 @@ To create gallery-quality computational art that lives and breathes, use the alg
### TECHNICAL REQUIREMENTS ### TECHNICAL REQUIREMENTS
**Seeded Randomness (Art Blocks Pattern)**: **Seeded Randomness (Art Blocks Pattern)**:
```javascript ```javascript
// ALWAYS use a seed for reproducibility // ALWAYS use a seed for reproducibility
let seed = 12345; // or hash from user input let seed = 12345; // or hash from user input
@@ -146,7 +156,7 @@ To establish parameters that emerge naturally from the algorithmic philosophy, c
```javascript ```javascript
let params = { let params = {
seed: 12345, // Always include seed for reproducibility seed: 12345, // Always include seed for reproducibility
// colors // colors
// Add parameters that control YOUR algorithm: // Add parameters that control YOUR algorithm:
// - Quantities (how many?) // - Quantities (how many?)
@@ -167,16 +177,19 @@ let params = {
To express the philosophy through code, avoid thinking "which pattern should I use?" and instead think "how to express this philosophy through code?" To express the philosophy through code, avoid thinking "which pattern should I use?" and instead think "how to express this philosophy through code?"
If the philosophy is about **organic emergence**, consider using: If the philosophy is about **organic emergence**, consider using:
- Elements that accumulate or grow over time - Elements that accumulate or grow over time
- Random processes constrained by natural rules - Random processes constrained by natural rules
- Feedback loops and interactions - Feedback loops and interactions
If the philosophy is about **mathematical beauty**, consider using: If the philosophy is about **mathematical beauty**, consider using:
- Geometric relationships and ratios - Geometric relationships and ratios
- Trigonometric functions and harmonics - Trigonometric functions and harmonics
- Precise calculations creating unexpected patterns - Precise calculations creating unexpected patterns
If the philosophy is about **controlled chaos**, consider using: If the philosophy is about **controlled chaos**, consider using:
- Random variation within strict boundaries - Random variation within strict boundaries
- Bifurcation and phase transitions - Bifurcation and phase transitions
- Order emerging from disorder - Order emerging from disorder
@@ -186,6 +199,7 @@ If the philosophy is about **controlled chaos**, consider using:
To guide the implementation, let the conceptual essence inform creative and original choices. Build something that expresses the vision for this particular request. To guide the implementation, let the conceptual essence inform creative and original choices. Build something that expresses the vision for this particular request.
**Canvas Setup**: Standard p5.js structure: **Canvas Setup**: Standard p5.js structure:
```javascript ```javascript
function setup() { function setup() {
createCanvas(1200, 1200); createCanvas(1200, 1200);
@@ -211,6 +225,7 @@ function draw() {
### OUTPUT FORMAT ### OUTPUT FORMAT
Output: Output:
1. **Algorithmic Philosophy** - As markdown or text explaining the generative aesthetic 1. **Algorithmic Philosophy** - As markdown or text explaining the generative aesthetic
2. **Single HTML Artifact** - Self-contained interactive generative art built from `templates/viewer.html` (see STEP 0 and next section) 2. **Single HTML Artifact** - Self-contained interactive generative art built from `templates/viewer.html` (see STEP 0 and next section)
@@ -229,6 +244,7 @@ To allow exploration of the generative art, create a single, self-contained HTML
The `templates/viewer.html` file is the foundation. It contains the exact structure and styling needed. The `templates/viewer.html` file is the foundation. It contains the exact structure and styling needed.
**FIXED (always include exactly as shown):** **FIXED (always include exactly as shown):**
- Layout structure (header, sidebar, main canvas area) - Layout structure (header, sidebar, main canvas area)
- Anthropic branding (UI colors, fonts, gradients) - Anthropic branding (UI colors, fonts, gradients)
- Seed section in sidebar: - Seed section in sidebar:
@@ -241,6 +257,7 @@ The `templates/viewer.html` file is the foundation. It contains the exact struct
- Reset button - Reset button
**VARIABLE (customize for each artwork):** **VARIABLE (customize for each artwork):**
- The entire p5.js algorithm (setup/draw/classes) - The entire p5.js algorithm (setup/draw/classes)
- The parameters object (define what the art needs) - The parameters object (define what the art needs)
- The Parameters section in sidebar: - The Parameters section in sidebar:
@@ -259,12 +276,14 @@ The `templates/viewer.html` file is the foundation. It contains the exact struct
### REQUIRED FEATURES ### REQUIRED FEATURES
**1. Parameter Controls** **1. Parameter Controls**
- Sliders for numeric parameters (particle count, noise scale, speed, etc.) - Sliders for numeric parameters (particle count, noise scale, speed, etc.)
- Color pickers for palette colors - Color pickers for palette colors
- Real-time updates when parameters change - Real-time updates when parameters change
- Reset button to restore defaults - Reset button to restore defaults
**2. Seed Navigation** **2. Seed Navigation**
- Display current seed number - Display current seed number
- "Previous" and "Next" buttons to cycle through seeds - "Previous" and "Next" buttons to cycle through seeds
- "Random" button for random seed - "Random" button for random seed
@@ -272,30 +291,31 @@ The `templates/viewer.html` file is the foundation. It contains the exact struct
- Generate 100 variations when requested (seeds 1-100) - Generate 100 variations when requested (seeds 1-100)
**3. Single Artifact Structure** **3. Single Artifact Structure**
```html ```html
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- p5.js from CDN - always available --> <!-- p5.js from CDN - always available -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
<style> <style>
/* All styling inline - clean, minimal */ /* All styling inline - clean, minimal */
/* Canvas on top, controls below */ /* Canvas on top, controls below */
</style> </style>
</head> </head>
<body> <body>
<div id="canvas-container"></div> <div id="canvas-container"></div>
<div id="controls"> <div id="controls">
<!-- All parameter controls --> <!-- All parameter controls -->
</div> </div>
<script> <script>
// ALL p5.js code inline here // ALL p5.js code inline here
// Parameter objects, classes, functions // Parameter objects, classes, functions
// setup() and draw() // setup() and draw()
// UI handlers // UI handlers
// Everything self-contained // Everything self-contained
</script> </script>
</body> </body>
</html> </html>
``` ```
@@ -306,30 +326,44 @@ The `templates/viewer.html` file is the foundation. It contains the exact struct
The sidebar structure: The sidebar structure:
**1. Seed (FIXED)** - Always include exactly as shown: **1. Seed (FIXED)** - Always include exactly as shown:
- Seed display - Seed display
- Prev/Next/Random/Jump buttons - Prev/Next/Random/Jump buttons
**2. Parameters (VARIABLE)** - Create controls for the art: **2. Parameters (VARIABLE)** - Create controls for the art:
```html ```html
<div class="control-group"> <div class="control-group">
<label>Parameter Name</label> <label>Parameter Name</label>
<input type="range" id="param" min="..." max="..." step="..." value="..." oninput="updateParam('param', this.value)"> <input
<span class="value-display" id="param-value">...</span> type="range"
id="param"
min="..."
max="..."
step="..."
value="..."
oninput="updateParam('param', this.value)"
/>
<span class="value-display" id="param-value">...</span>
</div> </div>
``` ```
Add as many control-group divs as there are parameters. Add as many control-group divs as there are parameters.
**3. Colors (OPTIONAL/VARIABLE)** - Include if the art needs adjustable colors: **3. Colors (OPTIONAL/VARIABLE)** - Include if the art needs adjustable colors:
- Add color pickers if users should control palette - Add color pickers if users should control palette
- Skip this section if the art uses fixed colors - Skip this section if the art uses fixed colors
- Skip if the art is monochrome - Skip if the art is monochrome
**4. Actions (FIXED)** - Always include exactly as shown: **4. Actions (FIXED)** - Always include exactly as shown:
- Regenerate button - Regenerate button
- Reset button - Reset button
- Download PNG button - Download PNG button
**Requirements**: **Requirements**:
- Seed controls must work (prev/next/random/jump/display) - Seed controls must work (prev/next/random/jump/display)
- All parameters must have UI controls - All parameters must have UI controls
- Regenerate, Reset, Download buttons must work - Regenerate, Reset, Download buttons must work
@@ -338,6 +372,7 @@ Add as many control-group divs as there are parameters.
### USING THE ARTIFACT ### USING THE ARTIFACT
The HTML artifact works immediately: The HTML artifact works immediately:
1. **In claude.ai**: Displayed as an interactive artifact - runs instantly 1. **In claude.ai**: Displayed as an interactive artifact - runs instantly
2. **As a file**: Save and open in any browser - no server needed 2. **As a file**: Save and open in any browser - no server needed
3. **Sharing**: Send the HTML file - it's completely self-contained 3. **Sharing**: Send the HTML file - it's completely self-contained
@@ -369,11 +404,13 @@ Each request is unique. The process involves:
5. **Build matching UI controls** - Sliders/inputs for those parameters 5. **Build matching UI controls** - Sliders/inputs for those parameters
**The constants**: **The constants**:
- Anthropic branding (colors, fonts, layout) - Anthropic branding (colors, fonts, layout)
- Seed navigation (always present) - Seed navigation (always present)
- Self-contained HTML artifact - Self-contained HTML artifact
**Everything else is variable**: **Everything else is variable**:
- The algorithm itself - The algorithm itself
- The parameters - The parameters
- The UI controls - The UI controls
@@ -399,7 +436,8 @@ This skill includes helpful templates and documentation:
- Embed algorithms inline in the HTML artifact (don't create separate .js files) - Embed algorithms inline in the HTML artifact (don't create separate .js files)
**Critical reminder**: **Critical reminder**:
- The **template is the STARTING POINT**, not inspiration - The **template is the STARTING POINT**, not inspiration
- The **algorithm is where to create** something unique - The **algorithm is where to create** something unique
- Don't copy the flow field example - build what the philosophy demands - Don't copy the flow field example - build what the philosophy demands
- But DO keep the exact UI structure and Anthropic branding from the template - But DO keep the exact UI structure and Anthropic branding from the template
@@ -24,21 +24,25 @@ Before implementing tracking, understand:
## Core Principles ## Core Principles
### 1. Track for Decisions, Not Data ### 1. Track for Decisions, Not Data
- Every event should inform a decision - Every event should inform a decision
- Avoid vanity metrics - Avoid vanity metrics
- Quality > quantity of events - Quality > quantity of events
### 2. Start with the Questions ### 2. Start with the Questions
- What do you need to know? - What do you need to know?
- What actions will you take based on this data? - What actions will you take based on this data?
- Work backwards to what you need to track - Work backwards to what you need to track
### 3. Name Things Consistently ### 3. Name Things Consistently
- Naming conventions matter - Naming conventions matter
- Establish patterns before implementing - Establish patterns before implementing
- Document everything - Document everything
### 4. Maintain Data Quality ### 4. Maintain Data Quality
- Validate implementation - Validate implementation
- Monitor for issues - Monitor for issues
- Clean data > more data - Clean data > more data
@@ -56,12 +60,12 @@ Event Name | Category | Properties | Trigger | Notes
### Event Types ### Event Types
| Type | Examples | | Type | Examples |
|------|----------| | ------------------ | ------------------------------------------------ |
| Pageviews | Automatic, enhanced with metadata | | Pageviews | Automatic, enhanced with metadata |
| User Actions | Button clicks, form submissions, feature usage | | User Actions | Button clicks, form submissions, feature usage |
| System Events | Signup completed, purchase, subscription changed | | System Events | Signup completed, purchase, subscription changed |
| Custom Conversions | Goal completions, funnel stages | | Custom Conversions | Goal completions, funnel stages |
**For comprehensive event lists**: See [references/event-library.md](references/event-library.md) **For comprehensive event lists**: See [references/event-library.md](references/event-library.md)
@@ -80,6 +84,7 @@ checkout_payment_completed
``` ```
### Best Practices ### Best Practices
- Lowercase with underscores - Lowercase with underscores
- Be specific: `cta_hero_clicked` vs. `button_clicked` - Be specific: `cta_hero_clicked` vs. `button_clicked`
- Include context in properties, not event name - Include context in properties, not event name
@@ -92,21 +97,21 @@ checkout_payment_completed
### Marketing Site ### Marketing Site
| Event | Properties | | Event | Properties |
|-------|------------| | ---------------- | --------------------- |
| cta_clicked | button_text, location | | cta_clicked | button_text, location |
| form_submitted | form_type | | form_submitted | form_type |
| signup_completed | method, source | | signup_completed | method, source |
| demo_requested | - | | demo_requested | - |
### Product/App ### Product/App
| Event | Properties | | Event | Properties |
|-------|------------| | ------------------------- | ---------------------- |
| onboarding_step_completed | step_number, step_name | | onboarding_step_completed | step_number, step_name |
| feature_used | feature_name | | feature_used | feature_name |
| purchase_completed | plan, value | | purchase_completed | plan, value |
| subscription_cancelled | reason | | subscription_cancelled | reason |
**For full event library by business type**: See [references/event-library.md](references/event-library.md) **For full event library by business type**: See [references/event-library.md](references/event-library.md)
@@ -116,14 +121,15 @@ checkout_payment_completed
### Standard Properties ### Standard Properties
| Category | Properties | | Category | Properties |
|----------|------------| | -------- | ----------------------------------------- |
| Page | page_title, page_location, page_referrer | | Page | page_title, page_location, page_referrer |
| User | user_id, user_type, account_id, plan_type | | User | user_id, user_type, account_id, plan_type |
| Campaign | source, medium, campaign, content, term | | Campaign | source, medium, campaign, content, term |
| Product | product_id, product_name, category, price | | Product | product_id, product_name, category, price |
### Best Practices ### Best Practices
- Use consistent property names - Use consistent property names
- Include relevant context - Include relevant context
- Don't duplicate automatic properties - Don't duplicate automatic properties
@@ -145,8 +151,8 @@ checkout_payment_completed
```javascript ```javascript
gtag('event', 'signup_completed', { gtag('event', 'signup_completed', {
'method': 'email', method: 'email',
'plan': 'free' plan: 'free',
}); });
``` ```
@@ -158,19 +164,19 @@ gtag('event', 'signup_completed', {
### Container Structure ### Container Structure
| Component | Purpose | | Component | Purpose |
|-----------|---------| | --------- | --------------------------------------- |
| Tags | Code that executes (GA4, pixels) | | Tags | Code that executes (GA4, pixels) |
| Triggers | When tags fire (page view, click) | | Triggers | When tags fire (page view, click) |
| Variables | Dynamic values (click text, data layer) | | Variables | Dynamic values (click text, data layer) |
### Data Layer Pattern ### Data Layer Pattern
```javascript ```javascript
dataLayer.push({ dataLayer.push({
'event': 'form_submitted', event: 'form_submitted',
'form_name': 'contact', form_name: 'contact',
'form_location': 'footer' form_location: 'footer',
}); });
``` ```
@@ -182,15 +188,16 @@ dataLayer.push({
### Standard Parameters ### Standard Parameters
| Parameter | Purpose | Example | | Parameter | Purpose | Example |
|-----------|---------|---------| | ------------ | ---------------------- | ------------------ |
| utm_source | Traffic source | google, newsletter | | utm_source | Traffic source | google, newsletter |
| utm_medium | Marketing medium | cpc, email, social | | utm_medium | Marketing medium | cpc, email, social |
| utm_campaign | Campaign name | spring_sale | | utm_campaign | Campaign name | spring_sale |
| utm_content | Differentiate versions | hero_cta | | utm_content | Differentiate versions | hero_cta |
| utm_term | Paid search keywords | running+shoes | | utm_term | Paid search keywords | running+shoes |
### Naming Conventions ### Naming Conventions
- Lowercase everything - Lowercase everything
- Use underscores or hyphens consistently - Use underscores or hyphens consistently
- Be specific but concise: `blog_footer_cta`, not `cta1` - Be specific but concise: `blog_footer_cta`, not `cta1`
@@ -202,10 +209,10 @@ dataLayer.push({
### Testing Tools ### Testing Tools
| Tool | Use For | | Tool | Use For |
|------|---------| | ------------------ | ---------------------------------- |
| GA4 DebugView | Real-time event monitoring | | GA4 DebugView | Real-time event monitoring |
| GTM Preview Mode | Test triggers before publish | | GTM Preview Mode | Test triggers before publish |
| Browser Extensions | Tag Assistant, dataLayer Inspector | | Browser Extensions | Tag Assistant, dataLayer Inspector |
### Validation Checklist ### Validation Checklist
@@ -219,23 +226,25 @@ dataLayer.push({
### Common Issues ### Common Issues
| Issue | Check | | Issue | Check |
|-------|-------| | ----------------- | ----------------------------------------- |
| Events not firing | Trigger config, GTM loaded | | Events not firing | Trigger config, GTM loaded |
| Wrong values | Variable path, data layer structure | | Wrong values | Variable path, data layer structure |
| Duplicate events | Multiple containers, trigger firing twice | | Duplicate events | Multiple containers, trigger firing twice |
--- ---
## Privacy and Compliance ## Privacy and Compliance
### Considerations ### Considerations
- Cookie consent required in EU/UK/CA - Cookie consent required in EU/UK/CA
- No PII in analytics properties - No PII in analytics properties
- Data retention settings - Data retention settings
- User deletion capabilities - User deletion capabilities
### Implementation ### Implementation
- Use consent mode (wait for consent) - Use consent mode (wait for consent)
- IP anonymization - IP anonymization
- Only collect what you need - Only collect what you need
@@ -251,26 +260,27 @@ dataLayer.push({
# [Site/Product] Tracking Plan # [Site/Product] Tracking Plan
## Overview ## Overview
- Tools: GA4, GTM - Tools: GA4, GTM
- Last updated: [Date] - Last updated: [Date]
## Events ## Events
| Event Name | Description | Properties | Trigger | | Event Name | Description | Properties | Trigger |
|------------|-------------|------------|---------| | ---------------- | --------------------- | ------------ | ------------ |
| signup_completed | User completes signup | method, plan | Success page | | signup_completed | User completes signup | method, plan | Success page |
## Custom Dimensions ## Custom Dimensions
| Name | Scope | Parameter | | Name | Scope | Parameter |
|------|-------|-----------| | --------- | ----- | --------- |
| user_type | User | user_type | | user_type | User | user_type |
## Conversions ## Conversions
| Conversion | Event | Counting | | Conversion | Event | Counting |
|------------|-------|----------| | ---------- | ---------------- | ---------------- |
| Signup | signup_completed | Once per session | | Signup | signup_completed | Once per session |
``` ```
--- ---
@@ -290,13 +300,13 @@ dataLayer.push({
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key analytics tools: For implementation, see the [tools registry](../../tools/REGISTRY.md). Key analytics tools:
| Tool | Best For | MCP | Guide | | Tool | Best For | MCP | Guide |
|------|----------|:---:|-------| | ------------- | ------------------------------------- | :-: | ----------------------------------------------------- |
| **GA4** | Web analytics, Google ecosystem | ✓ | [ga4.md](../../tools/integrations/ga4.md) | | **GA4** | Web analytics, Google ecosystem | | [ga4.md](../../tools/integrations/ga4.md) |
| **Mixpanel** | Product analytics, event tracking | - | [mixpanel.md](../../tools/integrations/mixpanel.md) | | **Mixpanel** | Product analytics, event tracking | - | [mixpanel.md](../../tools/integrations/mixpanel.md) |
| **Amplitude** | Product analytics, cohort analysis | - | [amplitude.md](../../tools/integrations/amplitude.md) | | **Amplitude** | Product analytics, cohort analysis | - | [amplitude.md](../../tools/integrations/amplitude.md) |
| **PostHog** | Open-source analytics, session replay | - | [posthog.md](../../tools/integrations/posthog.md) | | **PostHog** | Open-source analytics, session replay | - | [posthog.md](../../tools/integrations/posthog.md) |
| **Segment** | Customer data platform, routing | - | [segment.md](../../tools/integrations/segment.md) | | **Segment** | Customer data platform, routing | - | [segment.md](../../tools/integrations/segment.md) |
--- ---
@@ -6,36 +6,36 @@ Comprehensive list of events to track by business type and context.
### Navigation & Engagement ### Navigation & Engagement
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | --------------------- | -------------------------- | ---------------------------------------- |
| page_view | Page loaded (enhanced) | page_title, page_location, content_group | | page_view | Page loaded (enhanced) | page_title, page_location, content_group |
| scroll_depth | User scrolled to threshold | depth (25, 50, 75, 100) | | scroll_depth | User scrolled to threshold | depth (25, 50, 75, 100) |
| outbound_link_clicked | Click to external site | link_url, link_text | | outbound_link_clicked | Click to external site | link_url, link_text |
| internal_link_clicked | Click within site | link_url, link_text, location | | internal_link_clicked | Click within site | link_url, link_text, location |
| video_played | Video started | video_id, video_title, duration | | video_played | Video started | video_id, video_title, duration |
| video_completed | Video finished | video_id, video_title, duration | | video_completed | Video finished | video_id, video_title, duration |
### CTA & Form Interactions ### CTA & Form Interactions
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | -------------------- | ---------------------- | ------------------------------- |
| cta_clicked | Call to action clicked | button_text, cta_location, page | | cta_clicked | Call to action clicked | button_text, cta_location, page |
| form_started | User began form | form_name, form_location | | form_started | User began form | form_name, form_location |
| form_field_completed | Field filled | form_name, field_name | | form_field_completed | Field filled | form_name, field_name |
| form_submitted | Form successfully sent | form_name, form_location | | form_submitted | Form successfully sent | form_name, form_location |
| form_error | Form validation failed | form_name, error_type | | form_error | Form validation failed | form_name, error_type |
| resource_downloaded | Asset downloaded | resource_name, resource_type | | resource_downloaded | Asset downloaded | resource_name, resource_type |
### Conversion Events ### Conversion Events
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | --------------------- | ------------------- | ---------------------- |
| signup_started | Initiated signup | source, page | | signup_started | Initiated signup | source, page |
| signup_completed | Finished signup | method, plan, source | | signup_completed | Finished signup | method, plan, source |
| demo_requested | Demo form submitted | company_size, industry | | demo_requested | Demo form submitted | company_size, industry |
| contact_submitted | Contact form sent | inquiry_type | | contact_submitted | Contact form sent | inquiry_type |
| newsletter_subscribed | Email list signup | source, list_name | | newsletter_subscribed | Email list signup | source, list_name |
| trial_started | Free trial began | plan, source | | trial_started | Free trial began | plan, source |
--- ---
@@ -43,37 +43,37 @@ Comprehensive list of events to track by business type and context.
### Onboarding ### Onboarding
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | -------------------------- | ----------------------- | --------------------------------- |
| signup_completed | Account created | method, referral_source | | signup_completed | Account created | method, referral_source |
| onboarding_started | Began onboarding | - | | onboarding_started | Began onboarding | - |
| onboarding_step_completed | Step finished | step_number, step_name | | onboarding_step_completed | Step finished | step_number, step_name |
| onboarding_completed | All steps done | steps_completed, time_to_complete | | onboarding_completed | All steps done | steps_completed, time_to_complete |
| onboarding_skipped | User skipped onboarding | step_skipped_at | | onboarding_skipped | User skipped onboarding | step_skipped_at |
| first_key_action_completed | Aha moment reached | action_type | | first_key_action_completed | Aha moment reached | action_type |
### Core Usage ### Core Usage
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ---------------- | --------------------- | ------------------------------ |
| session_started | App session began | session_number | | session_started | App session began | session_number |
| feature_used | Feature interaction | feature_name, feature_category | | feature_used | Feature interaction | feature_name, feature_category |
| action_completed | Core action done | action_type, count | | action_completed | Core action done | action_type, count |
| content_created | User created content | content_type | | content_created | User created content | content_type |
| content_edited | User modified content | content_type | | content_edited | User modified content | content_type |
| content_deleted | User removed content | content_type | | content_deleted | User removed content | content_type |
| search_performed | In-app search | query, results_count | | search_performed | In-app search | query, results_count |
| settings_changed | Settings modified | setting_name, new_value | | settings_changed | Settings modified | setting_name, new_value |
| invite_sent | User invited others | invite_type, count | | invite_sent | User invited others | invite_type, count |
### Errors & Support ### Errors & Support
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------ | -------------------- | ------------------------------- |
| error_occurred | Error experienced | error_type, error_message, page | | error_occurred | Error experienced | error_type, error_message, page |
| help_opened | Help accessed | help_type, page | | help_opened | Help accessed | help_type, page |
| support_contacted | Support request made | contact_method, issue_type | | support_contacted | Support request made | contact_method, issue_type |
| feedback_submitted | User feedback given | feedback_type, rating | | feedback_submitted | User feedback given | feedback_type, rating |
--- ---
@@ -81,26 +81,26 @@ Comprehensive list of events to track by business type and context.
### Pricing & Checkout ### Pricing & Checkout
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | -------------------- | ------------------- | ------------------------------------- |
| pricing_viewed | Pricing page seen | source | | pricing_viewed | Pricing page seen | source |
| plan_selected | Plan chosen | plan_name, billing_cycle | | plan_selected | Plan chosen | plan_name, billing_cycle |
| checkout_started | Began checkout | plan, value | | checkout_started | Began checkout | plan, value |
| payment_info_entered | Payment submitted | payment_method | | payment_info_entered | Payment submitted | payment_method |
| purchase_completed | Purchase successful | plan, value, currency, transaction_id | | purchase_completed | Purchase successful | plan, value, currency, transaction_id |
| purchase_failed | Purchase failed | error_reason, plan | | purchase_failed | Purchase failed | error_reason, plan |
### Subscription Management ### Subscription Management
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ----------------------- | ---------------------- | ------------------------- |
| trial_started | Trial began | plan, trial_length | | trial_started | Trial began | plan, trial_length |
| trial_ended | Trial expired | plan, converted (bool) | | trial_ended | Trial expired | plan, converted (bool) |
| subscription_upgraded | Plan upgraded | from_plan, to_plan, value | | subscription_upgraded | Plan upgraded | from_plan, to_plan, value |
| subscription_downgraded | Plan downgraded | from_plan, to_plan | | subscription_downgraded | Plan downgraded | from_plan, to_plan |
| subscription_cancelled | Cancelled | plan, reason, tenure | | subscription_cancelled | Cancelled | plan, reason, tenure |
| subscription_renewed | Renewed | plan, value | | subscription_renewed | Renewed | plan, value |
| billing_updated | Payment method changed | - | | billing_updated | Payment method changed | - |
--- ---
@@ -108,41 +108,41 @@ Comprehensive list of events to track by business type and context.
### Browsing ### Browsing
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------- | -------------------- | ----------------------------------------- |
| product_viewed | Product page viewed | product_id, product_name, category, price | | product_viewed | Product page viewed | product_id, product_name, category, price |
| product_list_viewed | Category/list viewed | list_name, products[] | | product_list_viewed | Category/list viewed | list_name, products[] |
| product_searched | Search performed | query, results_count | | product_searched | Search performed | query, results_count |
| product_filtered | Filters applied | filter_type, filter_value | | product_filtered | Filters applied | filter_type, filter_value |
| product_sorted | Sort applied | sort_by, sort_order | | product_sorted | Sort applied | sort_by, sort_order |
### Cart ### Cart
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------------- | ---------------- | ----------------------------------------- |
| product_added_to_cart | Item added | product_id, product_name, price, quantity | | product_added_to_cart | Item added | product_id, product_name, price, quantity |
| product_removed_from_cart | Item removed | product_id, product_name, price, quantity | | product_removed_from_cart | Item removed | product_id, product_name, price, quantity |
| cart_viewed | Cart page viewed | cart_value, items_count | | cart_viewed | Cart page viewed | cart_value, items_count |
### Checkout ### Checkout
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ----------------------- | --------------- | ---------------------------------------- |
| checkout_started | Checkout began | cart_value, items_count | | checkout_started | Checkout began | cart_value, items_count |
| checkout_step_completed | Step finished | step_number, step_name | | checkout_step_completed | Step finished | step_number, step_name |
| shipping_info_entered | Address entered | shipping_method | | shipping_info_entered | Address entered | shipping_method |
| payment_info_entered | Payment entered | payment_method | | payment_info_entered | Payment entered | payment_method |
| coupon_applied | Coupon used | coupon_code, discount_value | | coupon_applied | Coupon used | coupon_code, discount_value |
| purchase_completed | Order placed | transaction_id, value, currency, items[] | | purchase_completed | Order placed | transaction_id, value, currency, items[] |
### Post-Purchase ### Post-Purchase
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ---------------- | ------------------- | ---------------------- |
| order_confirmed | Confirmation viewed | transaction_id | | order_confirmed | Confirmation viewed | transaction_id |
| refund_requested | Refund initiated | transaction_id, reason | | refund_requested | Refund initiated | transaction_id, reason |
| refund_completed | Refund processed | transaction_id, value | | refund_completed | Refund processed | transaction_id, value |
| review_submitted | Product reviewed | product_id, rating | | review_submitted | Product reviewed | product_id, rating |
--- ---
@@ -150,30 +150,30 @@ Comprehensive list of events to track by business type and context.
### Team & Collaboration ### Team & Collaboration
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------- | ------------------- | --------------------------- |
| team_created | New team/org made | team_size, plan | | team_created | New team/org made | team_size, plan |
| team_member_invited | Invite sent | role, invite_method | | team_member_invited | Invite sent | role, invite_method |
| team_member_joined | Member accepted | role | | team_member_joined | Member accepted | role |
| team_member_removed | Member removed | role | | team_member_removed | Member removed | role |
| role_changed | Permissions updated | user_id, old_role, new_role | | role_changed | Permissions updated | user_id, old_role, new_role |
### Integration Events ### Integration Events
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------------ | ---------------------- | ------------------------ |
| integration_viewed | Integration page seen | integration_name | | integration_viewed | Integration page seen | integration_name |
| integration_started | Setup began | integration_name | | integration_started | Setup began | integration_name |
| integration_connected | Successfully connected | integration_name | | integration_connected | Successfully connected | integration_name |
| integration_disconnected | Removed integration | integration_name, reason | | integration_disconnected | Removed integration | integration_name, reason |
### Account Events ### Account Events
| Event Name | Description | Properties | | Event Name | Description | Properties |
|------------|-------------|------------| | ------------------- | ----------------- | ------------------------- |
| account_created | New account | source, plan | | account_created | New account | source, plan |
| account_upgraded | Plan upgrade | from_plan, to_plan | | account_upgraded | Plan upgrade | from_plan, to_plan |
| account_churned | Account closed | reason, tenure, mrr_lost | | account_churned | Account closed | reason, tenure, mrr_lost |
| account_reactivated | Returned customer | previous_tenure, new_plan | | account_reactivated | Returned customer | previous_tenure, new_plan |
--- ---
@@ -183,6 +183,7 @@ Comprehensive list of events to track by business type and context.
### Standard Properties to Include ### Standard Properties to Include
**User Context:** **User Context:**
``` ```
user_id: "12345" user_id: "12345"
user_type: "free" | "trial" | "paid" user_type: "free" | "trial" | "paid"
@@ -191,6 +192,7 @@ plan_type: "starter" | "pro" | "enterprise"
``` ```
**Session Context:** **Session Context:**
``` ```
session_id: "sess_abc" session_id: "sess_abc"
session_number: 5 session_number: 5
@@ -199,6 +201,7 @@ referrer: "https://google.com"
``` ```
**Campaign Context:** **Campaign Context:**
``` ```
source: "google" source: "google"
medium: "cpc" medium: "cpc"
@@ -207,6 +210,7 @@ content: "hero_cta"
``` ```
**Product Context (E-commerce):** **Product Context (E-commerce):**
``` ```
product_id: "SKU123" product_id: "SKU123"
product_name: "Product Name" product_name: "Product Name"
@@ -217,6 +221,7 @@ currency: "USD"
``` ```
**Timing:** **Timing:**
``` ```
timestamp: "2024-01-15T10:30:00Z" timestamp: "2024-01-15T10:30:00Z"
time_on_page: 45 time_on_page: 45
@@ -228,6 +233,7 @@ session_duration: 300
## Funnel Event Sequences ## Funnel Event Sequences
### Signup Funnel ### Signup Funnel
1. signup_started 1. signup_started
2. signup_step_completed (email) 2. signup_step_completed (email)
3. signup_step_completed (password) 3. signup_step_completed (password)
@@ -235,6 +241,7 @@ session_duration: 300
5. onboarding_started 5. onboarding_started
### Purchase Funnel ### Purchase Funnel
1. pricing_viewed 1. pricing_viewed
2. plan_selected 2. plan_selected
3. checkout_started 3. checkout_started
@@ -242,6 +249,7 @@ session_duration: 300
5. purchase_completed 5. purchase_completed
### E-commerce Funnel ### E-commerce Funnel
1. product_viewed 1. product_viewed
2. product_added_to_cart 2. product_added_to_cart
3. cart_viewed 3. cart_viewed
@@ -13,25 +13,27 @@ Detailed implementation guide for Google Analytics 4.
### Enhanced Measurement Events (Automatic) ### Enhanced Measurement Events (Automatic)
| Event | Description | Configuration | | Event | Description | Configuration |
|-------|-------------|---------------| | ---------------- | ------------------------ | ----------------------- |
| page_view | Page loads | Automatic | | page_view | Page loads | Automatic |
| scroll | 90% scroll depth | Toggle on/off | | scroll | 90% scroll depth | Toggle on/off |
| outbound_click | Click to external domain | Automatic | | outbound_click | Click to external domain | Automatic |
| site_search | Search query used | Configure parameter | | site_search | Search query used | Configure parameter |
| video_engagement | YouTube video plays | Toggle on/off | | video_engagement | YouTube video plays | Toggle on/off |
| file_download | PDF, docs, etc. | Configurable extensions | | file_download | PDF, docs, etc. | Configurable extensions |
### Recommended Events ### Recommended Events
Use Google's predefined events when possible for enhanced reporting: Use Google's predefined events when possible for enhanced reporting:
**All properties:** **All properties:**
- login, sign_up - login, sign_up
- share - share
- search - search
**E-commerce:** **E-commerce:**
- view_item, view_item_list - view_item, view_item_list
- add_to_cart, remove_from_cart - add_to_cart, remove_from_cart
- begin_checkout - begin_checkout
@@ -39,6 +41,7 @@ Use Google's predefined events when possible for enhanced reporting:
- purchase, refund - purchase, refund
**Games:** **Games:**
- level_up, unlock_achievement - level_up, unlock_achievement
- post_score, spend_virtual_currency - post_score, spend_virtual_currency
@@ -53,31 +56,33 @@ Reference: https://support.google.com/analytics/answer/9267735
```javascript ```javascript
// Basic event // Basic event
gtag('event', 'signup_completed', { gtag('event', 'signup_completed', {
'method': 'email', method: 'email',
'plan': 'free' plan: 'free',
}); });
// Event with value // Event with value
gtag('event', 'purchase', { gtag('event', 'purchase', {
'transaction_id': 'T12345', transaction_id: 'T12345',
'value': 99.99, value: 99.99,
'currency': 'USD', currency: 'USD',
'items': [{ items: [
'item_id': 'SKU123', {
'item_name': 'Product Name', item_id: 'SKU123',
'price': 99.99 item_name: 'Product Name',
}] price: 99.99,
},
],
}); });
// User properties // User properties
gtag('set', 'user_properties', { gtag('set', 'user_properties', {
'user_type': 'premium', user_type: 'premium',
'plan_name': 'pro' plan_name: 'pro',
}); });
// User ID (for logged-in users) // User ID (for logged-in users)
gtag('config', 'GA_MEASUREMENT_ID', { gtag('config', 'GA_MEASUREMENT_ID', {
'user_id': 'USER_ID' user_id: 'USER_ID',
}); });
``` ```
@@ -86,40 +91,42 @@ gtag('config', 'GA_MEASUREMENT_ID', {
```javascript ```javascript
// Custom event // Custom event
dataLayer.push({ dataLayer.push({
'event': 'signup_completed', event: 'signup_completed',
'method': 'email', method: 'email',
'plan': 'free' plan: 'free',
}); });
// Set user properties // Set user properties
dataLayer.push({ dataLayer.push({
'user_id': '12345', user_id: '12345',
'user_type': 'premium' user_type: 'premium',
}); });
// E-commerce purchase // E-commerce purchase
dataLayer.push({ dataLayer.push({
'event': 'purchase', event: 'purchase',
'ecommerce': { ecommerce: {
'transaction_id': 'T12345', transaction_id: 'T12345',
'value': 99.99, value: 99.99,
'currency': 'USD', currency: 'USD',
'items': [{ items: [
'item_id': 'SKU123', {
'item_name': 'Product Name', item_id: 'SKU123',
'price': 99.99, item_name: 'Product Name',
'quantity': 1 price: 99.99,
}] quantity: 1,
} },
],
},
}); });
// Clear ecommerce before sending (best practice) // Clear ecommerce before sending (best practice)
dataLayer.push({ ecommerce: null }); dataLayer.push({ ecommerce: null });
dataLayer.push({ dataLayer.push({
'event': 'view_item', event: 'view_item',
'ecommerce': { ecommerce: {
// ... // ...
} },
}); });
``` ```
@@ -141,8 +148,8 @@ dataLayer.push({
```javascript ```javascript
// Event with conversion value // Event with conversion value
gtag('event', 'purchase', { gtag('event', 'purchase', {
'value': 99.99, value: 99.99,
'currency': 'USD' currency: 'USD',
}); });
``` ```
@@ -155,11 +162,13 @@ Or set default value in GA4 Admin when marking conversion.
### When to Use ### When to Use
**Custom dimensions:** **Custom dimensions:**
- Properties you want to segment/filter by - Properties you want to segment/filter by
- User attributes (plan type, industry) - User attributes (plan type, industry)
- Content attributes (author, category) - Content attributes (author, category)
**Custom metrics:** **Custom metrics:**
- Numeric values to aggregate - Numeric values to aggregate
- Scores, counts, durations - Scores, counts, durations
@@ -175,11 +184,11 @@ Or set default value in GA4 Admin when marking conversion.
### Examples ### Examples
| Dimension | Scope | Parameter | Description | | Dimension | Scope | Parameter | Description |
|-----------|-------|-----------|-------------| | ---------------- | ----- | ------------- | ------------------- |
| User Type | User | user_type | Free, trial, paid | | User Type | User | user_type | Free, trial, paid |
| Content Author | Event | author | Blog post author | | Content Author | Event | author | Blog post author |
| Product Category | Item | item_category | E-commerce category | | Product Category | Item | item_category | E-commerce category |
--- ---
@@ -190,6 +199,7 @@ Or set default value in GA4 Admin when marking conversion.
Admin > Data display > Audiences Admin > Data display > Audiences
**Use cases:** **Use cases:**
- Remarketing audiences (export to Ads) - Remarketing audiences (export to Ads)
- Segment analysis - Segment analysis
- Trigger-based events - Trigger-based events
@@ -197,15 +207,18 @@ Admin > Data display > Audiences
### Audience Examples ### Audience Examples
**High-intent visitors:** **High-intent visitors:**
- Viewed pricing page - Viewed pricing page
- Did not convert - Did not convert
- In last 7 days - In last 7 days
**Engaged users:** **Engaged users:**
- 3+ sessions - 3+ sessions
- Or 5+ minutes total engagement - Or 5+ minutes total engagement
**Purchasers:** **Purchasers:**
- Purchase event - Purchase event
- For exclusion or lookalike - For exclusion or lookalike
@@ -216,6 +229,7 @@ Admin > Data display > Audiences
### DebugView ### DebugView
Enable with: Enable with:
- URL parameter: `?debug_mode=true` - URL parameter: `?debug_mode=true`
- Chrome extension: GA Debugger - Chrome extension: GA Debugger
- gtag: `'debug_mode': true` in config - gtag: `'debug_mode': true` in config
@@ -230,16 +244,19 @@ Reports > Real-time
### Common Issues ### Common Issues
**Events not appearing:** **Events not appearing:**
- Check DebugView first - Check DebugView first
- Verify gtag/GTM firing - Verify gtag/GTM firing
- Check filter exclusions - Check filter exclusions
**Parameter values missing:** **Parameter values missing:**
- Custom dimension not created - Custom dimension not created
- Parameter name mismatch - Parameter name mismatch
- Data still processing (24-48 hrs) - Data still processing (24-48 hrs)
**Conversions not recording:** **Conversions not recording:**
- Event not marked as conversion - Event not marked as conversion
- Event name doesn't match - Event name doesn't match
- Counting method (once vs. every) - Counting method (once vs. every)
@@ -253,6 +270,7 @@ Reports > Real-time
Admin > Data streams > [Stream] > Configure tag settings > Define internal traffic Admin > Data streams > [Stream] > Configure tag settings > Define internal traffic
**Exclude:** **Exclude:**
- Internal IP addresses - Internal IP addresses
- Developer traffic - Developer traffic
- Testing environments - Testing environments
@@ -285,6 +303,7 @@ Admin > Data streams > [Stream] > Configure tag settings
### Audience Export ### Audience Export
Audiences created in GA4 can be used in Google Ads for: Audiences created in GA4 can be used in Google Ads for:
- Remarketing campaigns - Remarketing campaigns
- Customer match - Customer match
- Similar audiences - Similar audiences
@@ -9,6 +9,7 @@ Detailed guide for implementing tracking via Google Tag Manager.
Tags are code snippets that execute when triggered. Tags are code snippets that execute when triggered.
**Common tag types:** **Common tag types:**
- GA4 Configuration (base setup) - GA4 Configuration (base setup)
- GA4 Event (custom events) - GA4 Event (custom events)
- Google Ads Conversion - Google Ads Conversion
@@ -21,6 +22,7 @@ Tags are code snippets that execute when triggered.
Triggers define when tags fire. Triggers define when tags fire.
**Built-in triggers:** **Built-in triggers:**
- Page View: All Pages, DOM Ready, Window Loaded - Page View: All Pages, DOM Ready, Window Loaded
- Click: All Elements, Just Links - Click: All Elements, Just Links
- Form Submission - Form Submission
@@ -29,6 +31,7 @@ Triggers define when tags fire.
- Element Visibility - Element Visibility
**Custom triggers:** **Custom triggers:**
- Custom Event (from dataLayer) - Custom Event (from dataLayer)
- Trigger Groups (multiple conditions) - Trigger Groups (multiple conditions)
@@ -37,12 +40,14 @@ Triggers define when tags fire.
Variables capture dynamic values. Variables capture dynamic values.
**Built-in (enable as needed):** **Built-in (enable as needed):**
- Click Text, Click URL, Click ID, Click Classes - Click Text, Click URL, Click ID, Click Classes
- Page Path, Page URL, Page Hostname - Page Path, Page URL, Page Hostname
- Referrer - Referrer
- Form Element, Form ID - Form Element, Form ID
**User-defined:** **User-defined:**
- Data Layer variables - Data Layer variables
- JavaScript variables - JavaScript variables
- Lookup tables - Lookup tables
@@ -88,9 +93,9 @@ window.dataLayer = window.dataLayer || [];
// Push event // Push event
dataLayer.push({ dataLayer.push({
'event': 'event_name', event: 'event_name',
'property1': 'value1', property1: 'value1',
'property2': 'value2' property2: 'value2',
}); });
``` ```
@@ -100,24 +105,24 @@ dataLayer.push({
// Set on page load (before GTM container) // Set on page load (before GTM container)
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
dataLayer.push({ dataLayer.push({
'pageType': 'product', pageType: 'product',
'contentGroup': 'products', contentGroup: 'products',
'user': { user: {
'loggedIn': true, loggedIn: true,
'userId': '12345', userId: '12345',
'userType': 'premium' userType: 'premium',
} },
}); });
``` ```
### Form Submission ### Form Submission
```javascript ```javascript
document.querySelector('#contact-form').addEventListener('submit', function() { document.querySelector('#contact-form').addEventListener('submit', function () {
dataLayer.push({ dataLayer.push({
'event': 'form_submitted', event: 'form_submitted',
'formName': 'contact', formName: 'contact',
'formLocation': 'footer' formLocation: 'footer',
}); });
}); });
``` ```
@@ -125,11 +130,11 @@ document.querySelector('#contact-form').addEventListener('submit', function() {
### Button Click ### Button Click
```javascript ```javascript
document.querySelector('.cta-button').addEventListener('click', function() { document.querySelector('.cta-button').addEventListener('click', function () {
dataLayer.push({ dataLayer.push({
'event': 'cta_clicked', event: 'cta_clicked',
'ctaText': this.innerText, ctaText: this.innerText,
'ctaLocation': 'hero' ctaLocation: 'hero',
}); });
}); });
``` ```
@@ -140,49 +145,55 @@ document.querySelector('.cta-button').addEventListener('click', function() {
// Product view // Product view
dataLayer.push({ ecommerce: null }); // Clear previous dataLayer.push({ ecommerce: null }); // Clear previous
dataLayer.push({ dataLayer.push({
'event': 'view_item', event: 'view_item',
'ecommerce': { ecommerce: {
'items': [{ items: [
'item_id': 'SKU123', {
'item_name': 'Product Name', item_id: 'SKU123',
'price': 99.99, item_name: 'Product Name',
'item_category': 'Category', price: 99.99,
'quantity': 1 item_category: 'Category',
}] quantity: 1,
} },
],
},
}); });
// Add to cart // Add to cart
dataLayer.push({ ecommerce: null }); dataLayer.push({ ecommerce: null });
dataLayer.push({ dataLayer.push({
'event': 'add_to_cart', event: 'add_to_cart',
'ecommerce': { ecommerce: {
'items': [{ items: [
'item_id': 'SKU123', {
'item_name': 'Product Name', item_id: 'SKU123',
'price': 99.99, item_name: 'Product Name',
'quantity': 1 price: 99.99,
}] quantity: 1,
} },
],
},
}); });
// Purchase // Purchase
dataLayer.push({ ecommerce: null }); dataLayer.push({ ecommerce: null });
dataLayer.push({ dataLayer.push({
'event': 'purchase', event: 'purchase',
'ecommerce': { ecommerce: {
'transaction_id': 'T12345', transaction_id: 'T12345',
'value': 99.99, value: 99.99,
'currency': 'USD', currency: 'USD',
'tax': 5.00, tax: 5.0,
'shipping': 10.00, shipping: 10.0,
'items': [{ items: [
'item_id': 'SKU123', {
'item_name': 'Product Name', item_id: 'SKU123',
'price': 99.99, item_name: 'Product Name',
'quantity': 1 price: 99.99,
}] quantity: 1,
} },
],
},
}); });
``` ```
@@ -195,6 +206,7 @@ dataLayer.push({
**Tag Type:** Google Analytics: GA4 Configuration **Tag Type:** Google Analytics: GA4 Configuration
**Settings:** **Settings:**
- Measurement ID: G-XXXXXXXX - Measurement ID: G-XXXXXXXX
- Send page view: Checked (for pageviews) - Send page view: Checked (for pageviews)
- User Properties: Add any user-level dimensions - User Properties: Add any user-level dimensions
@@ -206,6 +218,7 @@ dataLayer.push({
**Tag Type:** Google Analytics: GA4 Event **Tag Type:** Google Analytics: GA4 Event
**Settings:** **Settings:**
- Configuration Tag: Select your config tag - Configuration Tag: Select your config tag
- Event Name: {{DL - event_name}} or hardcode - Event Name: {{DL - event_name}} or hardcode
- Event Parameters: Add parameters from dataLayer - Event Parameters: Add parameters from dataLayer
@@ -218,14 +231,22 @@ dataLayer.push({
```html ```html
<script> <script>
!function(f,b,e,v,n,t,s) !(function (f, b, e, v, n, t, s) {
{if(f.fbq)return;n=f.fbq=function(){n.callMethod? if (f.fbq) return;
n.callMethod.apply(n,arguments):n.queue.push(arguments)}; n = f.fbq = function () {
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);
n.queue=[];t=b.createElement(e);t.async=!0; };
t.src=v;s=b.getElementsByTagName(e)[0]; if (!f._fbq) f._fbq = n;
s.parentNode.insertBefore(t,s)}(window, document,'script', n.push = n;
'https://connect.facebook.net/en_US/fbevents.js'); n.loaded = !0;
n.version = '2.0';
n.queue = [];
t = b.createElement(e);
t.async = !0;
t.src = v;
s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s);
})(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'YOUR_PIXEL_ID'); fbq('init', 'YOUR_PIXEL_ID');
fbq('track', 'PageView'); fbq('track', 'PageView');
</script> </script>
@@ -240,7 +261,7 @@ dataLayer.push({
```html ```html
<script> <script>
fbq('track', 'Lead', { fbq('track', 'Lead', {
content_name: '{{DL - form_name}}' content_name: '{{DL - form_name}}',
}); });
</script> </script>
``` ```
@@ -258,6 +279,7 @@ dataLayer.push({
3. GTM debug panel opens at bottom 3. GTM debug panel opens at bottom
**What to check:** **What to check:**
- Tags fired on this event - Tags fired on this event
- Tags not fired (and why) - Tags not fired (and why)
- Variables and their values - Variables and their values
@@ -266,16 +288,19 @@ dataLayer.push({
### Debug Tips ### Debug Tips
**Tag not firing:** **Tag not firing:**
- Check trigger conditions - Check trigger conditions
- Verify data layer push - Verify data layer push
- Check tag sequencing - Check tag sequencing
**Wrong variable value:** **Wrong variable value:**
- Check data layer structure - Check data layer structure
- Verify variable path (nested objects) - Verify variable path (nested objects)
- Check timing (data may not exist yet) - Check timing (data may not exist yet)
**Multiple firings:** **Multiple firings:**
- Check trigger uniqueness - Check trigger uniqueness
- Look for duplicate tags - Look for duplicate tags
- Check tag firing options - Check tag firing options
@@ -287,6 +312,7 @@ dataLayer.push({
### Workspaces ### Workspaces
Use workspaces for team collaboration: Use workspaces for team collaboration:
- Default workspace for production - Default workspace for production
- Separate workspaces for large changes - Separate workspaces for large changes
- Merge when ready - Merge when ready
@@ -294,12 +320,14 @@ Use workspaces for team collaboration:
### Version Management ### Version Management
**Best practices:** **Best practices:**
- Name every version descriptively - Name every version descriptively
- Add notes explaining changes - Add notes explaining changes
- Review changes before publish - Review changes before publish
- Keep production version noted - Keep production version noted
**Version notes example:** **Version notes example:**
``` ```
v15: Added purchase conversion tracking v15: Added purchase conversion tracking
- New tag: GA4 - Event - Purchase - New tag: GA4 - Event - Purchase
@@ -317,15 +345,15 @@ v15: Added purchase conversion tracking
```javascript ```javascript
// Default state (before consent) // Default state (before consent)
gtag('consent', 'default', { gtag('consent', 'default', {
'analytics_storage': 'denied', analytics_storage: 'denied',
'ad_storage': 'denied' ad_storage: 'denied',
}); });
// Update on consent // Update on consent
function grantConsent() { function grantConsent() {
gtag('consent', 'update', { gtag('consent', 'update', {
'analytics_storage': 'granted', analytics_storage: 'granted',
'ad_storage': 'granted' ad_storage: 'granted',
}); });
} }
``` ```
@@ -346,6 +374,7 @@ function grantConsent() {
Tag Configuration > Advanced Settings > Tag Sequencing Tag Configuration > Advanced Settings > Tag Sequencing
**Use cases:** **Use cases:**
- Config tag before event tags - Config tag before event tags
- Pixel initialization before tracking - Pixel initialization before tracking
- Cleanup after conversion - Cleanup after conversion
@@ -353,6 +382,7 @@ Tag Configuration > Advanced Settings > Tag Sequencing
### Exception Handling ### Exception Handling
**Trigger exceptions** - Prevent tag from firing: **Trigger exceptions** - Prevent tag from firing:
- Exclude certain pages - Exclude certain pages
- Exclude internal traffic - Exclude internal traffic
- Exclude during testing - Exclude during testing
+25 -26
View File
@@ -3,7 +3,7 @@ name: antfu
description: Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences. description: Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences.
metadata: metadata:
author: Anthony Fu author: Anthony Fu
version: "2026.02.03" version: '2026.02.03'
--- ---
## Coding Practices ## Coding Practices
@@ -48,15 +48,15 @@ metadata:
### @antfu/ni Commands ### @antfu/ni Commands
| Command | Description | | Command | Description |
|---------|-------------| | -------------------------- | ------------------------------------------ |
| `ni` | Install dependencies | | `ni` | Install dependencies |
| `ni <pkg>` / `ni -D <pkg>` | Add dependency / dev dependency | | `ni <pkg>` / `ni -D <pkg>` | Add dependency / dev dependency |
| `nr <script>` | Run script | | `nr <script>` | Run script |
| `nu` | Upgrade dependencies | | `nu` | Upgrade dependencies |
| `nun <pkg>` | Uninstall dependency | | `nun <pkg>` | Uninstall dependency |
| `nci` | Clean install (`pnpm i --frozen-lockfile`) | | `nci` | Clean install (`pnpm i --frozen-lockfile`) |
| `nlx <pkg>` | Execute package (`npx`) | | `nlx <pkg>` | Execute package (`npx`) |
### TypeScript Config ### TypeScript Config
@@ -80,12 +80,11 @@ metadata:
```js ```js
// eslint.config.mjs // eslint.config.mjs
import antfu from '@antfu/eslint-config' import antfu from '@antfu/eslint-config';
export default antfu() export default antfu();
``` ```
When completing tasks, run `pnpm run lint --fix` to format the code and fix coding style. When completing tasks, run `pnpm run lint --fix` to format the code and fix coding style.
For detailed configuration options: [antfu-eslint-config](references/antfu-eslint-config.md) For detailed configuration options: [antfu-eslint-config](references/antfu-eslint-config.md)
@@ -108,12 +107,12 @@ For detailed configuration options: [antfu-eslint-config](references/antfu-eslin
Use named catalogs in `pnpm-workspace.yaml` for version management: Use named catalogs in `pnpm-workspace.yaml` for version management:
| Catalog | Purpose | | Catalog | Purpose |
|---------|---------| | ---------- | ------------------------------------ |
| `prod` | Production dependencies | | `prod` | Production dependencies |
| `inlined` | Bundler-inlined dependencies | | `inlined` | Bundler-inlined dependencies |
| `dev` | Dev tools (linter, bundler, testing) | | `dev` | Dev tools (linter, bundler, testing) |
| `frontend` | Frontend libraries | | `frontend` | Frontend libraries |
Avoid the default catalog. Catalog names can be adjusted per project needs. Avoid the default catalog. Catalog names can be adjusted per project needs.
@@ -121,10 +120,10 @@ Avoid the default catalog. Catalog names can be adjusted per project needs.
## References ## References
| Topic | Description | Reference | | Topic | Description | Reference |
|-------|-------------|-----------| | ------------------- | --------------------------------------------------------------- | -------------------------------------------------------- |
| ESLint Config | Framework support, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) | | ESLint Config | Framework support, formatters, rule overrides, VS Code settings | [antfu-eslint-config](references/antfu-eslint-config.md) |
| Project Setup | .gitignore, GitHub Actions, VS Code extensions | [setting-up](references/setting-up.md) | | Project Setup | .gitignore, GitHub Actions, VS Code extensions | [setting-up](references/setting-up.md) |
| App Development | Vue/Nuxt/UnoCSS conventions and patterns | [app-development](references/app-development.md) | | App Development | Vue/Nuxt/UnoCSS conventions and patterns | [app-development](references/app-development.md) |
| Library Development | tsdown bundling, pure ESM publishing | [library-development](references/library-development.md) | | Library Development | tsdown bundling, pure ESM publishing | [library-development](references/library-development.md) |
| Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) | | Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) |
@@ -12,7 +12,7 @@ Handles both linting and formatting (no Prettier needed). Auto-detects TypeScrip
## Configuration Options ## Configuration Options
```js ```js
import antfu from '@antfu/eslint-config' import antfu from '@antfu/eslint-config';
export default antfu({ export default antfu({
// Project type: 'lib' for libraries, 'app' (default) for applications // Project type: 'lib' for libraries, 'app' (default) for applications
@@ -23,7 +23,7 @@ export default antfu({
// Stylistic options // Stylistic options
stylistic: { stylistic: {
indent: 2, // 2, 4, or 'tab' indent: 2, // 2, 4, or 'tab'
quotes: 'single', // or 'double' quotes: 'single', // or 'double'
}, },
@@ -34,7 +34,7 @@ export default antfu({
// Disable specific language support // Disable specific language support
jsonc: false, jsonc: false,
yaml: false, yaml: false,
}) });
``` ```
## Framework Support ## Framework Support
@@ -46,9 +46,9 @@ Vue accessibility:
```js ```js
export default antfu({ export default antfu({
vue: { vue: {
a11y: true a11y: true,
}, },
}) });
// Requires: pnpm add -D eslint-plugin-vuejs-accessibility // Requires: pnpm add -D eslint-plugin-vuejs-accessibility
``` ```
@@ -57,7 +57,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
react: true, react: true,
}) });
// Requires: pnpm add -D @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh // Requires: pnpm add -D @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh
``` ```
@@ -66,7 +66,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
nextjs: true, nextjs: true,
}) });
// Requires: pnpm add -D @next/eslint-plugin-next // Requires: pnpm add -D @next/eslint-plugin-next
``` ```
@@ -75,7 +75,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
svelte: true, svelte: true,
}) });
// Requires: pnpm add -D eslint-plugin-svelte // Requires: pnpm add -D eslint-plugin-svelte
``` ```
@@ -84,7 +84,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
astro: true, astro: true,
}) });
// Requires: pnpm add -D eslint-plugin-astro // Requires: pnpm add -D eslint-plugin-astro
``` ```
@@ -93,7 +93,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
solid: true, solid: true,
}) });
// Requires: pnpm add -D eslint-plugin-solid // Requires: pnpm add -D eslint-plugin-solid
``` ```
@@ -102,7 +102,7 @@ export default antfu({
```js ```js
export default antfu({ export default antfu({
unocss: true, unocss: true,
}) });
// Requires: pnpm add -D @unocss/eslint-plugin // Requires: pnpm add -D @unocss/eslint-plugin
``` ```
@@ -113,11 +113,11 @@ For files ESLint doesn't handle natively:
```js ```js
export default antfu({ export default antfu({
formatters: { formatters: {
css: true, // Format CSS, LESS, SCSS (uses Prettier) css: true, // Format CSS, LESS, SCSS (uses Prettier)
html: true, // Format HTML (uses Prettier) html: true, // Format HTML (uses Prettier)
markdown: 'prettier' // or 'dprint' markdown: 'prettier', // or 'dprint'
} },
}) });
// Requires: pnpm add -D eslint-plugin-format // Requires: pnpm add -D eslint-plugin-format
``` ```
@@ -135,8 +135,8 @@ export default antfu(
rules: { rules: {
'style/semi': ['error', 'never'], 'style/semi': ['error', 'never'],
}, },
} },
) );
``` ```
### Per-integration overrides ### Per-integration overrides
@@ -153,7 +153,7 @@ export default antfu({
'ts/consistent-type-definitions': ['error', 'interface'], 'ts/consistent-type-definitions': ['error', 'interface'],
}, },
}, },
}) });
``` ```
### File-specific overrides ### File-specific overrides
@@ -166,29 +166,29 @@ export default antfu(
rules: { rules: {
'vue/operator-linebreak': ['error', 'before'], 'vue/operator-linebreak': ['error', 'before'],
}, },
} },
) );
``` ```
## Plugin Prefix Renaming ## Plugin Prefix Renaming
The config renames plugin prefixes for consistency: The config renames plugin prefixes for consistency:
| New Prefix | Original | | New Prefix | Original |
|------------|----------| | ---------- | ---------------------- |
| `ts/*` | `@typescript-eslint/*` | | `ts/*` | `@typescript-eslint/*` |
| `style/*` | `@stylistic/*` | | `style/*` | `@stylistic/*` |
| `import/*` | `import-lite/*` | | `import/*` | `import-lite/*` |
| `node/*` | `n/*` | | `node/*` | `n/*` |
| `yaml/*` | `yml/*` | | `yaml/*` | `yml/*` |
| `test/*` | `vitest/*` | | `test/*` | `vitest/*` |
| `next/*` | `@next/next` | | `next/*` | `@next/next` |
Use the new prefix when overriding or disabling rules: Use the new prefix when overriding or disabling rules:
```ts ```ts
// eslint-disable-next-line ts/consistent-type-definitions // eslint-disable-next-line ts/consistent-type-definitions
type Foo = { bar: 2 } type Foo = { bar: 2 };
``` ```
## Type-Aware Rules ## Type-Aware Rules
@@ -200,7 +200,7 @@ export default antfu({
typescript: { typescript: {
tsconfigPath: 'tsconfig.json', tsconfigPath: 'tsconfig.json',
}, },
}) });
``` ```
## Config Composer API ## Config Composer API
@@ -213,11 +213,11 @@ export default antfu()
.override('antfu/stylistic/rules', { .override('antfu/stylistic/rules', {
rules: { rules: {
'style/generator-star-spacing': ['error', { after: true, before: false }], 'style/generator-star-spacing': ['error', { after: true, before: false }],
} },
}) })
.renamePlugins({ .renamePlugins({
'old-prefix': 'new-prefix', 'old-prefix': 'new-prefix',
}) });
``` ```
## Less Opinionated Mode ## Less Opinionated Mode
@@ -226,8 +226,8 @@ Disable Anthony's most opinionated rules:
```js ```js
export default antfu({ export default antfu({
lessOpinionated: true lessOpinionated: true,
}) });
``` ```
## Lint-Staged Setup ## Lint-Staged Setup
@@ -258,7 +258,7 @@ Add to `.vscode/settings.json`:
"editor.formatOnSave": false, "editor.formatOnSave": false,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",
"source.organizeImports": "never" "source.organizeImports": "never",
}, },
"eslint.rules.customizations": [ "eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true }, { "rule": "style/*", "severity": "off", "fixable": true },
@@ -270,7 +270,7 @@ Add to `.vscode/settings.json`:
{ "rule": "*-dangle", "severity": "off", "fixable": true }, { "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true }, { "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true }, { "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true } { "rule": "*semi", "severity": "off", "fixable": true },
], ],
"eslint.validate": [ "eslint.validate": [
"javascript", "javascript",
@@ -289,12 +289,12 @@ Add to `.vscode/settings.json`:
"svelte", "svelte",
"css", "css",
"less", "less",
"scss" "scss",
] ],
} }
``` ```
<!-- <!--
Source references: Source references:
- https://github.com/antfu/eslint-config - https://github.com/antfu/eslint-config
- https://raw.githubusercontent.com/antfu/eslint-config/refs/heads/main/README.md - https://raw.githubusercontent.com/antfu/eslint-config/refs/heads/main/README.md
@@ -7,39 +7,39 @@ description: Vue/Nuxt/UnoCSS application conventions. Use when building web apps
## Framework Selection ## Framework Selection
| Use Case | Choice | | Use Case | Choice |
|----------|--------| | ------------------------------------------------------ | ---------- |
| SPA, client-only, library playgrounds | Vite + Vue | | SPA, client-only, library playgrounds | Vite + Vue |
| SSR, SSG, SEO-critical, file-based routing, API routes | Nuxt | | SSR, SSG, SEO-critical, file-based routing, API routes | Nuxt |
## Vue Conventions ## Vue Conventions
| Convention | Preference | | Convention | Preference |
|------------|------------| | ------------- | ---------------------------------- |
| Script syntax | Always `<script setup lang="ts">` | | Script syntax | Always `<script setup lang="ts">` |
| State | Prefer `shallowRef()` over `ref()` | | State | Prefer `shallowRef()` over `ref()` |
| Objects | Use `ref()`, avoid `reactive()` | | Objects | Use `ref()`, avoid `reactive()` |
| Styling | UnoCSS | | Styling | UnoCSS |
| Utilities | VueUse | | Utilities | VueUse |
### Props and Emits ### Props and Emits
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
title: string title: string;
count?: number count?: number;
} }
interface Emits { interface Emits {
(e: 'update', value: number): void (e: 'update', value: number): void;
(e: 'close'): void (e: 'close'): void;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
count: 0, count: 0,
}) });
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>();
</script> </script>
``` ```
@@ -5,11 +5,11 @@ description: Building and publishing TypeScript libraries with tsdown. Use when
# Library Development # Library Development
| Aspect | Choice | | Aspect | Choice |
|--------|--------| | ------- | ------------------------- |
| Bundler | tsdown | | Bundler | tsdown |
| Output | Pure ESM only (no CJS) | | Output | Pure ESM only (no CJS) |
| DTS | Generated via tsdown | | DTS | Generated via tsdown |
| Exports | Auto-generated via tsdown | | Exports | Auto-generated via tsdown |
## tsdown Configuration ## tsdown Configuration
@@ -18,34 +18,31 @@ Use tsdown with these options enabled:
```ts ```ts
// tsdown.config.ts // tsdown.config.ts
import { defineConfig } from 'tsdown' import { defineConfig } from 'tsdown';
export default defineConfig({ export default defineConfig({
entry: ['src/index.ts'], entry: ['src/index.ts'],
format: ['esm'], format: ['esm'],
dts: true, dts: true,
exports: true, exports: true,
}) });
``` ```
| Option | Value | Purpose | | Option | Value | Purpose |
|--------|-------|---------| | --------- | --------- | --------------------------------------------- |
| `format` | `['esm']` | Pure ESM, no CommonJS | | `format` | `['esm']` | Pure ESM, no CommonJS |
| `dts` | `true` | Generate `.d.ts` files | | `dts` | `true` | Generate `.d.ts` files |
| `exports` | `true` | Auto-update `exports` field in `package.json` | | `exports` | `true` | Auto-update `exports` field in `package.json` |
### Multiple Entry Points ### Multiple Entry Points
```ts ```ts
export default defineConfig({ export default defineConfig({
entry: [ entry: ['src/index.ts', 'src/utils.ts'],
'src/index.ts',
'src/utils.ts',
],
format: ['esm'], format: ['esm'],
dts: true, dts: true,
exports: true, exports: true,
}) });
``` ```
The `exports: true` option auto-generates the `exports` field in `package.json` when running `tsdown`. The `exports: true` option auto-generates the `exports` field in `package.json` when running `tsdown`.
@@ -45,7 +45,6 @@ In each package's `package.json`, add the scripts.
## ESLint Cache ## ESLint Cache
```json ```json
{ {
"scripts": { "scripts": {
@@ -66,38 +65,35 @@ For better DX across Vite, Nuxt, Vitest configs, create a centralized `alias.ts`
```ts ```ts
// alias.ts // alias.ts
import fs from 'node:fs' import fs from 'node:fs';
import { fileURLToPath } from 'node:url' import { fileURLToPath } from 'node:url';
import { join, relative } from 'pathe' import { join, relative } from 'pathe';
const root = fileURLToPath(new URL('.', import.meta.url)) const root = fileURLToPath(new URL('.', import.meta.url));
const r = (path: string) => fileURLToPath(new URL(`./packages/${path}`, import.meta.url)) const r = (path: string) => fileURLToPath(new URL(`./packages/${path}`, import.meta.url));
export const alias = { export const alias = {
'@myorg/core': r('core/src/index.ts'), '@myorg/core': r('core/src/index.ts'),
'@myorg/utils': r('utils/src/index.ts'), '@myorg/utils': r('utils/src/index.ts'),
'@myorg/ui': r('ui/src/index.ts'), '@myorg/ui': r('ui/src/index.ts'),
// Add more aliases as needed // Add more aliases as needed
} };
// Auto-update tsconfig.alias.json paths // Auto-update tsconfig.alias.json paths
const raw = fs.readFileSync(join(root, 'tsconfig.alias.json'), 'utf-8').trim() const raw = fs.readFileSync(join(root, 'tsconfig.alias.json'), 'utf-8').trim();
const tsconfig = JSON.parse(raw) const tsconfig = JSON.parse(raw);
tsconfig.compilerOptions.paths = Object.fromEntries( tsconfig.compilerOptions.paths = Object.fromEntries(
Object.entries(alias).map(([key, value]) => [key, [`./${relative(root, value)}`]]), Object.entries(alias).map(([key, value]) => [key, [`./${relative(root, value)}`]]),
) );
const newRaw = JSON.stringify(tsconfig, null, 2) const newRaw = JSON.stringify(tsconfig, null, 2);
if (newRaw !== raw) if (newRaw !== raw) fs.writeFileSync(join(root, 'tsconfig.alias.json'), `${newRaw}\n`, 'utf-8');
fs.writeFileSync(join(root, 'tsconfig.alias.json'), `${newRaw}\n`, 'utf-8')
``` ```
Then update the `tsconfig.json` to use the alias file: Then update the `tsconfig.json` to use the alias file:
```json ```json
{ {
"extends": [ "extends": ["./tsconfig.alias.json"]
"./tsconfig.alias.json"
]
} }
``` ```
@@ -107,18 +103,18 @@ Reference the centralized alias in all config files:
```ts ```ts
// vite.config.ts // vite.config.ts
import { alias } from './alias' import { alias } from './alias';
export default defineConfig({ export default defineConfig({
resolve: { alias }, resolve: { alias },
}) });
``` ```
```ts ```ts
// nuxt.config.ts // nuxt.config.ts
import { alias } from './alias' import { alias } from './alias';
export default defineNuxtConfig({ export default defineNuxtConfig({
alias, alias,
}) });
``` ```
@@ -109,11 +109,11 @@ Configure in `.vscode/extensions.json`:
} }
``` ```
| Extension | Description | | Extension | Description |
|-----------|-------------| | ------------------------- | --------------------------------------------- |
| `dbaeumer.vscode-eslint` | ESLint integration for linting and formatting | | `dbaeumer.vscode-eslint` | ESLint integration for linting and formatting |
| `antfu.pnpm-catalog-lens` | Shows pnpm catalog version hints inline | | `antfu.pnpm-catalog-lens` | Shows pnpm catalog version hints inline |
| `antfu.iconify` | Iconify icon preview and autocomplete | | `antfu.iconify` | Iconify icon preview and autocomplete |
| `antfu.unocss` | UnoCSS IntelliSense and syntax highlighting | | `antfu.unocss` | UnoCSS IntelliSense and syntax highlighting |
| `antfu.slidev` | Slidev preview and syntax highlighting | | `antfu.slidev` | Slidev preview and syntax highlighting |
| `vue.volar` | Vue Language Features | | `vue.volar` | Vue Language Features |
@@ -14,15 +14,18 @@ Better Auth is a TypeScript-first, framework-agnostic auth framework supporting
## Quick Reference ## Quick Reference
### Environment Variables ### Environment Variables
- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32` - `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`) - `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
Only define `baseURL`/`secret` in config if env vars are NOT set. Only define `baseURL`/`secret` in config if env vars are NOT set.
### File Location ### File Location
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path. CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
### CLI Commands ### CLI Commands
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter) - `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle - `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools - `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
@@ -33,18 +36,18 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
## Core Config Options ## Core Config Options
| Option | Notes | | Option | Notes |
|--------|-------| | ------------------ | ---------------------------------------------- |
| `appName` | Optional display name | | `appName` | Optional display name |
| `baseURL` | Only if `BETTER_AUTH_URL` not set | | `baseURL` | Only if `BETTER_AUTH_URL` not set |
| `basePath` | Default `/api/auth`. Set `/` for root. | | `basePath` | Default `/api/auth`. Set `/` for root. |
| `secret` | Only if `BETTER_AUTH_SECRET` not set | | `secret` | Only if `BETTER_AUTH_SECRET` not set |
| `database` | Required for most features. See adapters docs. | | `database` | Required for most features. See adapters docs. |
| `secondaryStorage` | Redis/KV for sessions & rate limits | | `secondaryStorage` | Redis/KV for sessions & rate limits |
| `emailAndPassword` | `{ enabled: true }` to activate | | `emailAndPassword` | `{ enabled: true }` to activate |
| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` | | `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
| `plugins` | Array of plugins | | `plugins` | Array of plugins |
| `trustedOrigins` | CSRF whitelist | | `trustedOrigins` | CSRF whitelist |
--- ---
@@ -61,11 +64,13 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
## Session Management ## Session Management
**Storage priority:** **Storage priority:**
1. If `secondaryStorage` defined → sessions go there (not DB) 1. If `secondaryStorage` defined → sessions go there (not DB)
2. Set `session.storeSessionInDatabase: true` to also persist to DB 2. Set `session.storeSessionInDatabase: true` to also persist to DB
3. No database + `cookieCache` → fully stateless mode 3. No database + `cookieCache` → fully stateless mode
**Cookie cache strategies:** **Cookie cache strategies:**
- `compact` (default) - Base64url + HMAC. Smallest. - `compact` (default) - Base64url + HMAC. Smallest.
- `jwt` - Standard JWT. Readable but signed. - `jwt` - Standard JWT. Readable but signed.
- `jwe` - Encrypted. Maximum security. - `jwe` - Encrypted. Maximum security.
@@ -95,9 +100,10 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
## Security ## Security
**In `advanced`:** **In `advanced`:**
- `useSecureCookies` - Force HTTPS cookies - `useSecureCookies` - Force HTTPS cookies
- `disableCSRFCheck` - ⚠️ Security risk - `disableCSRFCheck` - ⚠️ Security risk
- `disableOriginCheck` - ⚠️ Security risk - `disableOriginCheck` - ⚠️ Security risk
- `crossSubDomainCookies.enabled` - Share cookies across subdomains - `crossSubDomainCookies.enabled` - Share cookies across subdomains
- `ipAddress.ipAddressHeaders` - Custom IP headers for proxies - `ipAddress.ipAddressHeaders` - Custom IP headers for proxies
- `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false` - `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false`
@@ -119,9 +125,11 @@ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--c
## Plugins ## Plugins
**Import from dedicated paths for tree-shaking:** **Import from dedicated paths for tree-shaking:**
``` ```
import { twoFactor } from "better-auth/plugins/two-factor" import { twoFactor } from "better-auth/plugins/two-factor"
``` ```
NOT `from "better-auth/plugins"`. NOT `from "better-auth/plugins"`.
**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`. **Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
@@ -163,4 +171,4 @@ For separate client/server projects: `createAuthClient<typeof auth>()`.
- [Options Reference](https://better-auth.com/docs/reference/options) - [Options Reference](https://better-auth.com/docs/reference/options)
- [LLMs.txt](https://better-auth.com/llms.txt) - [LLMs.txt](https://better-auth.com/llms.txt)
- [GitHub](https://github.com/better-auth/better-auth) - [GitHub](https://github.com/better-auth/better-auth)
- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts) - [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)
@@ -1,6 +1,6 @@
--- ---
name: brainstorming name: brainstorming
description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation." description: 'You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.'
--- ---
# Brainstorming Ideas Into Designs # Brainstorming Ideas Into Designs
@@ -57,6 +57,7 @@ digraph brainstorming {
## The Process ## The Process
**Understanding the idea:** **Understanding the idea:**
- Check out the current project state first (files, docs, recent commits) - Check out the current project state first (files, docs, recent commits)
- Ask questions one at a time to refine the idea - Ask questions one at a time to refine the idea
- Prefer multiple choice questions when possible, but open-ended is fine too - Prefer multiple choice questions when possible, but open-ended is fine too
@@ -64,11 +65,13 @@ digraph brainstorming {
- Focus on understanding: purpose, constraints, success criteria - Focus on understanding: purpose, constraints, success criteria
**Exploring approaches:** **Exploring approaches:**
- Propose 2-3 different approaches with trade-offs - Propose 2-3 different approaches with trade-offs
- Present options conversationally with your recommendation and reasoning - Present options conversationally with your recommendation and reasoning
- Lead with your recommended option and explain why - Lead with your recommended option and explain why
**Presenting the design:** **Presenting the design:**
- Once you believe you understand what you're building, present the design - Once you believe you understand what you're building, present the design
- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced - Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced
- Ask after each section whether it looks right so far - Ask after each section whether it looks right so far
@@ -78,11 +81,13 @@ digraph brainstorming {
## After the Design ## After the Design
**Documentation:** **Documentation:**
- Write the validated design to `docs/plans/YYYY-MM-DD-<topic>-design.md` - Write the validated design to `docs/plans/YYYY-MM-DD-<topic>-design.md`
- Use elements-of-style:writing-clearly-and-concisely skill if available - Use elements-of-style:writing-clearly-and-concisely skill if available
- Commit the design document to git - Commit the design document to git
**Implementation:** **Implementation:**
- Invoke the writing-plans skill to create a detailed implementation plan - Invoke the writing-plans skill to create a detailed implementation plan
- Do NOT invoke any other skill. writing-plans is the next step. - Do NOT invoke any other skill. writing-plans is the next step.
@@ -7,6 +7,7 @@ license: Complete terms in LICENSE.txt
These are instructions for creating design philosophies - aesthetic movements that are then EXPRESSED VISUALLY. Output only .md files, .pdf files, and .png files. These are instructions for creating design philosophies - aesthetic movements that are then EXPRESSED VISUALLY. Output only .md files, .pdf files, and .png files.
Complete this in two steps: Complete this in two steps:
1. Design Philosophy Creation (.md file) 1. Design Philosophy Creation (.md file)
2. Express by creating it on a canvas (.pdf file or .png file) 2. Express by creating it on a canvas (.pdf file or .png file)
@@ -15,16 +16,19 @@ First, undertake this task:
## DESIGN PHILOSOPHY CREATION ## DESIGN PHILOSOPHY CREATION
To begin, create a VISUAL PHILOSOPHY (not layouts or templates) that will be interpreted through: To begin, create a VISUAL PHILOSOPHY (not layouts or templates) that will be interpreted through:
- Form, space, color, composition - Form, space, color, composition
- Images, graphics, shapes, patterns - Images, graphics, shapes, patterns
- Minimal text as visual accent - Minimal text as visual accent
### THE CRITICAL UNDERSTANDING ### THE CRITICAL UNDERSTANDING
- What is received: Some subtle input or instructions by the user that should be taken into account, but used as a foundation; it should not constrain creative freedom. - What is received: Some subtle input or instructions by the user that should be taken into account, but used as a foundation; it should not constrain creative freedom.
- What is created: A design philosophy/aesthetic movement. - What is created: A design philosophy/aesthetic movement.
- What happens next: Then, the same version receives the philosophy and EXPRESSES IT VISUALLY - creating artifacts that are 90% visual design, 10% essential text. - What happens next: Then, the same version receives the philosophy and EXPRESSES IT VISUALLY - creating artifacts that are 90% visual design, 10% essential text.
Consider this approach: Consider this approach:
- Write a manifesto for an art movement - Write a manifesto for an art movement
- The next phase involves making the artwork - The next phase involves making the artwork
@@ -37,6 +41,7 @@ The philosophy must emphasize: Visual expression. Spatial communication. Artisti
**Articulate the philosophy** (4-6 paragraphs - concise but complete): **Articulate the philosophy** (4-6 paragraphs - concise but complete):
To capture the VISUAL essence, express how the philosophy manifests through: To capture the VISUAL essence, express how the philosophy manifests through:
- Space and form - Space and form
- Color and material - Color and material
- Scale and rhythm - Scale and rhythm
@@ -44,6 +49,7 @@ To capture the VISUAL essence, express how the philosophy manifests through:
- Visual hierarchy - Visual hierarchy
**CRITICAL GUIDELINES:** **CRITICAL GUIDELINES:**
- **Avoid redundancy**: Each design aspect should be mentioned once. Avoid repeating points about color theory, spatial relationships, or typographic principles unless adding new depth. - **Avoid redundancy**: Each design aspect should be mentioned once. Avoid repeating points about color theory, spatial relationships, or typographic principles unless adding new depth.
- **Emphasize craftsmanship REPEATEDLY**: The philosophy MUST stress multiple times that the final work should appear as though it took countless hours to create, was labored over with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted," "the product of deep expertise," "painstaking attention," "master-level execution." - **Emphasize craftsmanship REPEATEDLY**: The philosophy MUST stress multiple times that the final work should appear as though it took countless hours to create, was labored over with care, and comes from someone at the absolute top of their field. This framing is essential - repeat phrases like "meticulously crafted," "the product of deep expertise," "painstaking attention," "master-level execution."
- **Leave creative space**: Remain specific about the aesthetic direction, but concise enough that the next Claude has room to make interpretive choices also at a extremely high level of craftmanship. - **Leave creative space**: Remain specific about the aesthetic direction, but concise enough that the next Claude has room to make interpretive choices also at a extremely high level of craftmanship.
@@ -72,9 +78,10 @@ Visual expression: Rounded forms, organic arrangements, color from nature throug
Philosophy: Pure order and restraint. Philosophy: Pure order and restraint.
Visual expression: Grid-based precision, bold photography or stark graphics, dramatic negative space. Typography precise but minimal - small essential text, large quiet zones. Swiss formalism meets Brutalist material honesty. Structure communicates, not words. Every alignment the work of countless refinements. Visual expression: Grid-based precision, bold photography or stark graphics, dramatic negative space. Typography precise but minimal - small essential text, large quiet zones. Swiss formalism meets Brutalist material honesty. Structure communicates, not words. Every alignment the work of countless refinements.
*These are condensed examples. The actual design philosophy should be 4-6 substantial paragraphs.* _These are condensed examples. The actual design philosophy should be 4-6 substantial paragraphs._
### ESSENTIAL PRINCIPLES ### ESSENTIAL PRINCIPLES
- **VISUAL PHILOSOPHY**: Create an aesthetic worldview to be expressed through design - **VISUAL PHILOSOPHY**: Create an aesthetic worldview to be expressed through design
- **MINIMAL TEXT**: Always emphasize that text is sparse, essential-only, integrated as visual element - never lengthy - **MINIMAL TEXT**: Always emphasize that text is sparse, essential-only, integrated as visual element - never lengthy
- **SPATIAL EXPRESSION**: Ideas communicate through space, form, color, composition - not paragraphs - **SPATIAL EXPRESSION**: Ideas communicate through space, form, color, composition - not paragraphs
@@ -103,13 +110,13 @@ With both the philosophy and the conceptual framework established, express it on
**IMPORTANT**: For any type of content, even if the user requests something for a movie/game/book, the approach should still be sophisticated. Never lose sight of the idea that this should be art, not something that's cartoony or amateur. **IMPORTANT**: For any type of content, even if the user requests something for a movie/game/book, the approach should still be sophisticated. Never lose sight of the idea that this should be art, not something that's cartoony or amateur.
To create museum or magazine quality work, use the design philosophy as the foundation. Create one single page, highly visual, design-forward PDF or PNG output (unless asked for more pages). Generally use repeating patterns and perfect shapes. Treat the abstract philosophical design as if it were a scientific bible, borrowing the visual language of systematic observation—dense accumulation of marks, repeated elements, or layered patterns that build meaning through patient repetition and reward sustained viewing. Add sparse, clinical typography and systematic reference markers that suggest this could be a diagram from an imaginary discipline, treating the invisible subject with the same reverence typically reserved for documenting observable phenomena. Anchor the piece with simple phrase(s) or details positioned subtly, using a limited color palette that feels intentional and cohesive. Embrace the paradox of using analytical visual language to express ideas about human experience: the result should feel like an artifact that proves something ephemeral can be studied, mapped, and understood through careful attention. This is true art. To create museum or magazine quality work, use the design philosophy as the foundation. Create one single page, highly visual, design-forward PDF or PNG output (unless asked for more pages). Generally use repeating patterns and perfect shapes. Treat the abstract philosophical design as if it were a scientific bible, borrowing the visual language of systematic observation—dense accumulation of marks, repeated elements, or layered patterns that build meaning through patient repetition and reward sustained viewing. Add sparse, clinical typography and systematic reference markers that suggest this could be a diagram from an imaginary discipline, treating the invisible subject with the same reverence typically reserved for documenting observable phenomena. Anchor the piece with simple phrase(s) or details positioned subtly, using a limited color palette that feels intentional and cohesive. Embrace the paradox of using analytical visual language to express ideas about human experience: the result should feel like an artifact that proves something ephemeral can be studied, mapped, and understood through careful attention. This is true art.
**Text as a contextual element**: Text is always minimal and visual-first, but let context guide whether that means whisper-quiet labels or bold typographic gestures. A punk venue poster might have larger, more aggressive type than a minimalist ceramics studio identity. Most of the time, font should be thin. All use of fonts must be design-forward and prioritize visual communication. Regardless of text scale, nothing falls off the page and nothing overlaps. Every element must be contained within the canvas boundaries with proper margins. Check carefully that all text, graphics, and visual elements have breathing room and clear separation. This is non-negotiable for professional execution. **IMPORTANT: Use different fonts if writing text. Search the `./canvas-fonts` directory. Regardless of approach, sophistication is non-negotiable.** **Text as a contextual element**: Text is always minimal and visual-first, but let context guide whether that means whisper-quiet labels or bold typographic gestures. A punk venue poster might have larger, more aggressive type than a minimalist ceramics studio identity. Most of the time, font should be thin. All use of fonts must be design-forward and prioritize visual communication. Regardless of text scale, nothing falls off the page and nothing overlaps. Every element must be contained within the canvas boundaries with proper margins. Check carefully that all text, graphics, and visual elements have breathing room and clear separation. This is non-negotiable for professional execution. **IMPORTANT: Use different fonts if writing text. Search the `./canvas-fonts` directory. Regardless of approach, sophistication is non-negotiable.**
Download and use whatever fonts are needed to make this a reality. Get creative by making the typography actually part of the art itself -- if the art is abstract, bring the font onto the canvas, not typeset digitally. Download and use whatever fonts are needed to make this a reality. Get creative by making the typography actually part of the art itself -- if the art is abstract, bring the font onto the canvas, not typeset digitally.
To push boundaries, follow design instinct/intuition while using the philosophy as a guiding principle. Embrace ultimate design freedom and choice. Push aesthetics and design to the frontier. To push boundaries, follow design instinct/intuition while using the philosophy as a guiding principle. Embrace ultimate design freedom and choice. Push aesthetics and design to the frontier.
**CRITICAL**: To achieve human-crafted quality (not AI-generated), create work that looks like it took countless hours. Make it appear as though someone at the absolute top of their field labored over every detail with painstaking care. Ensure the composition, spacing, color choices, typography - everything screams expert-level craftsmanship. Double-check that nothing overlaps, formatting is flawless, every detail perfect. Create something that could be shown to people to prove expertise and rank as undeniably impressive. **CRITICAL**: To achieve human-crafted quality (not AI-generated), create work that looks like it took countless hours. Make it appear as though someone at the absolute top of their field labored over every detail with painstaking care. Ensure the composition, spacing, color choices, typography - everything screams expert-level craftsmanship. Double-check that nothing overlaps, formatting is flawless, every detail perfect. Create something that could be shown to people to prove expertise and rank as undeniably impressive.
@@ -127,4 +134,4 @@ Take a second pass. Go back to the code and refine/polish further to make this a
## MULTI-PAGE OPTION ## MULTI-PAGE OPTION
To create additional pages when requested, create more creative pages along the same lines as the design philosophy but distinctly different as well. Bundle those pages in the same .pdf or many .pngs. Treat the first page as just a single page in a whole coffee table book waiting to be filled. Make the next pages unique twists and memories of the original. Have them almost tell a story in a very tasteful way. Exercise full creative freedom. To create additional pages when requested, create more creative pages along the same lines as the design philosophy but distinctly different as well. Bundle those pages in the same .pdf or many .pngs. Treat the first page as just a single page in a whole coffee table book waiting to be filled. Make the next pages unique twists and memories of the original. Have them almost tell a story in a very tasteful way. Exercise full creative freedom.
@@ -10,8 +10,8 @@ allowed-tools:
- Read - Read
- Grep - Grep
- Glob - Glob
- Bash # 运行 lint/test/build 命令验证代码质量 - Bash # 运行 lint/test/build 命令验证代码质量
- WebFetch # 查阅最新文档和最佳实践 - WebFetch # 查阅最新文档和最佳实践
--- ---
# Code Review Excellence # Code Review Excellence
@@ -34,6 +34,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
### 1. The Review Mindset ### 1. The Review Mindset
**Goals of Code Review:** **Goals of Code Review:**
- Catch bugs and edge cases - Catch bugs and edge cases
- Ensure code maintainability - Ensure code maintainability
- Share knowledge across team - Share knowledge across team
@@ -42,6 +43,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
- Build team culture - Build team culture
**Not the Goals:** **Not the Goals:**
- Show off knowledge - Show off knowledge
- Nitpick formatting (use linters) - Nitpick formatting (use linters)
- Block progress unnecessarily - Block progress unnecessarily
@@ -50,6 +52,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
### 2. Effective Feedback ### 2. Effective Feedback
**Good Feedback is:** **Good Feedback is:**
- Specific and actionable - Specific and actionable
- Educational, not judgmental - Educational, not judgmental
- Focused on the code, not the person - Focused on the code, not the person
@@ -59,20 +62,21 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
```markdown ```markdown
❌ Bad: "This is wrong." ❌ Bad: "This is wrong."
✅ Good: "This could cause a race condition when multiple users ✅ Good: "This could cause a race condition when multiple users
access simultaneously. Consider using a mutex here." access simultaneously. Consider using a mutex here."
❌ Bad: "Why didn't you use X pattern?" ❌ Bad: "Why didn't you use X pattern?"
✅ Good: "Have you considered the Repository pattern? It would ✅ Good: "Have you considered the Repository pattern? It would
make this easier to test. Here's an example: [link]" make this easier to test. Here's an example: [link]"
❌ Bad: "Rename this variable." ❌ Bad: "Rename this variable."
✅ Good: "[nit] Consider `userCount` instead of `uc` for ✅ Good: "[nit] Consider `userCount` instead of `uc` for
clarity. Not blocking if you prefer to keep it." clarity. Not blocking if you prefer to keep it."
``` ```
### 3. Review Scope ### 3. Review Scope
**What to Review:** **What to Review:**
- Logic correctness and edge cases - Logic correctness and edge cases
- Security vulnerabilities - Security vulnerabilities
- Performance implications - Performance implications
@@ -83,6 +87,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
- Architectural fit - Architectural fit
**What Not to Review Manually:** **What Not to Review Manually:**
- Code formatting (use Prettier, Black, etc.) - Code formatting (use Prettier, Black, etc.)
- Import organization - Import organization
- Linting violations - Linting violations
@@ -93,6 +98,7 @@ Transform code reviews from gatekeeping to knowledge sharing through constructiv
### Phase 1: Context Gathering (2-3 minutes) ### Phase 1: Context Gathering (2-3 minutes)
Before diving into code, understand: Before diving into code, understand:
1. Read PR description and linked issue 1. Read PR description and linked issue
2. Check PR size (>400 lines? Ask to split) 2. Check PR size (>400 lines? Ask to split)
3. Review CI/CD status (tests passing?) 3. Review CI/CD status (tests passing?)
@@ -113,6 +119,7 @@ Before diving into code, understand:
### Phase 3: Line-by-Line Review (10-20 minutes) ### Phase 3: Line-by-Line Review (10-20 minutes)
For each file, check: For each file, check:
- **Logic & Correctness** - Edge cases, off-by-one, null checks, race conditions - **Logic & Correctness** - Edge cases, off-by-one, null checks, race conditions
- **Security** - Input validation, injection risks, XSS, sensitive data - **Security** - Input validation, injection risks, XSS, sensitive data
- **Performance** - N+1 queries, unnecessary loops, memory leaks - **Performance** - N+1 queries, unnecessary loops, memory leaks
@@ -173,19 +180,19 @@ Use labels to indicate priority:
根据审查的代码语言,查阅对应的详细指南: 根据审查的代码语言,查阅对应的详细指南:
| Language/Framework | Reference File | Key Topics | | Language/Framework | Reference File | Key Topics |
|-------------------|----------------|------------| | ------------------ | ------------------------------------------- | -------------------------------------------------------------------- |
| **React** | [React Guide](reference/react.md) | Hooks, useEffect, React 19 Actions, RSC, Suspense, TanStack Query v5 | | **React** | [React Guide](reference/react.md) | Hooks, useEffect, React 19 Actions, RSC, Suspense, TanStack Query v5 |
| **Vue 3** | [Vue Guide](reference/vue.md) | Composition API, 响应性系统, Props/Emits, Watchers, Composables | | **Vue 3** | [Vue Guide](reference/vue.md) | Composition API, 响应性系统, Props/Emits, Watchers, Composables |
| **Rust** | [Rust Guide](reference/rust.md) | 所有权/借用, Unsafe 审查, 异步代码, 错误处理 | | **Rust** | [Rust Guide](reference/rust.md) | 所有权/借用, Unsafe 审查, 异步代码, 错误处理 |
| **TypeScript** | [TypeScript Guide](reference/typescript.md) | 类型安全, async/await, 不可变性 | | **TypeScript** | [TypeScript Guide](reference/typescript.md) | 类型安全, async/await, 不可变性 |
| **Python** | [Python Guide](reference/python.md) | 可变默认参数, 异常处理, 类属性 | | **Python** | [Python Guide](reference/python.md) | 可变默认参数, 异常处理, 类属性 |
| **Java** | [Java Guide](reference/java.md) | Java 17/21 新特性, Spring Boot 3, 虚拟线程, Stream/Optional | | **Java** | [Java Guide](reference/java.md) | Java 17/21 新特性, Spring Boot 3, 虚拟线程, Stream/Optional |
| **Go** | [Go Guide](reference/go.md) | 错误处理, goroutine/channel, context, 接口设计 | | **Go** | [Go Guide](reference/go.md) | 错误处理, goroutine/channel, context, 接口设计 |
| **C** | [C Guide](reference/c.md) | 指针/缓冲区, 内存安全, UB, 错误处理 | | **C** | [C Guide](reference/c.md) | 指针/缓冲区, 内存安全, UB, 错误处理 |
| **C++** | [C++ Guide](reference/cpp.md) | RAII, 生命周期, Rule of 0/3/5, 异常安全 | | **C++** | [C++ Guide](reference/cpp.md) | RAII, 生命周期, Rule of 0/3/5, 异常安全 |
| **CSS/Less/Sass** | [CSS Guide](reference/css-less-sass.md) | 变量规范, !important, 性能优化, 响应式, 兼容性 | | **CSS/Less/Sass** | [CSS Guide](reference/css-less-sass.md) | 变量规范, !important, 性能优化, 响应式, 兼容性 |
| **Qt** | [Qt Guide](reference/qt.md) | 对象模型, 信号/槽, 内存管理, 线程安全, 性能 | | **Qt** | [Qt Guide](reference/qt.md) | 对象模型, 信号/槽, 内存管理, 线程安全, 性能 |
## Additional Resources ## Additional Resources
@@ -20,15 +20,18 @@ Copy and use this template for your code reviews.
## Required Changes ## Required Changes
🔴 **[blocking]** [Issue description] 🔴 **[blocking]** [Issue description]
> [Code location or example] > [Code location or example]
> [Suggested fix or explanation] > [Suggested fix or explanation]
🔴 **[blocking]** [Issue description] 🔴 **[blocking]** [Issue description]
> [Details] > [Details]
## Important Suggestions ## Important Suggestions
🟡 **[important]** [Issue description] 🟡 **[important]** [Issue description]
> [Why this matters] > [Why this matters]
> [Suggested approach] > [Suggested approach]
@@ -68,6 +71,7 @@ Copy and use this template for your code reviews.
## Quick Copy Templates ## Quick Copy Templates
### Blocking Issue ### Blocking Issue
``` ```
🔴 **[blocking]** [Title] 🔴 **[blocking]** [Title]
@@ -82,6 +86,7 @@ Copy and use this template for your code reviews.
``` ```
### Important Suggestion ### Important Suggestion
``` ```
🟡 **[important]** [Title] 🟡 **[important]** [Title]
@@ -93,6 +98,7 @@ Copy and use this template for your code reviews.
``` ```
### Minor Suggestion ### Minor Suggestion
``` ```
🟢 **[nit]** [Suggestion] 🟢 **[nit]** [Suggestion]
@@ -100,6 +106,7 @@ Not blocking, but consider [improvement].
``` ```
### Praise ### Praise
``` ```
🎉 **[praise]** Great work on [specific thing]! 🎉 **[praise]** Great work on [specific thing]!
@@ -107,6 +114,7 @@ Not blocking, but consider [improvement].
``` ```
### Question ### Question
``` ```
❓ **[question]** [Your question] ❓ **[question]** [Your question]
@@ -70,38 +70,38 @@ Quick reference checklist for code reviews.
## Severity Labels ## Severity Labels
| Label | Meaning | Action | | Label | Meaning | Action |
|-------|---------|--------| | ----------------- | ------------ | ------------------- |
| 🔴 `[blocking]` | Must fix | Block merge | | 🔴 `[blocking]` | Must fix | Block merge |
| 🟡 `[important]` | Should fix | Discuss if disagree | | 🟡 `[important]` | Should fix | Discuss if disagree |
| 🟢 `[nit]` | Nice to have | Non-blocking | | 🟢 `[nit]` | Nice to have | Non-blocking |
| 💡 `[suggestion]` | Alternative | Consider | | 💡 `[suggestion]` | Alternative | Consider |
| ❓ `[question]` | Need clarity | Respond | | ❓ `[question]` | Need clarity | Respond |
| 🎉 `[praise]` | Good work | Celebrate! | | 🎉 `[praise]` | Good work | Celebrate! |
--- ---
## Decision Matrix ## Decision Matrix
| Situation | Decision | | Situation | Decision |
|-----------|----------| | --------------------------------- | ------------------------- |
| Critical security issue | 🔴 Block, fix immediately | | Critical security issue | 🔴 Block, fix immediately |
| Breaking change without migration | 🔴 Block | | Breaking change without migration | 🔴 Block |
| Missing error handling | 🟡 Should fix | | Missing error handling | 🟡 Should fix |
| No tests for new code | 🟡 Should fix | | No tests for new code | 🟡 Should fix |
| Style preference | 🟢 Non-blocking | | Style preference | 🟢 Non-blocking |
| Minor naming improvement | 🟢 Non-blocking | | Minor naming improvement | 🟢 Non-blocking |
| Clever but working code | 💡 Suggest simpler | | Clever but working code | 💡 Suggest simpler |
--- ---
## Time Budget ## Time Budget
| PR Size | Target Time | | PR Size | Target Time |
|---------|-------------| | ------------- | ------------ |
| < 100 lines | 10-15 min | | < 100 lines | 10-15 min |
| 100-400 lines | 20-40 min | | 100-400 lines | 20-40 min |
| > 400 lines | Ask to split | | > 400 lines | Ask to split |
--- ---
@@ -7,11 +7,13 @@
### S - 单一职责原则 (SRP) ### S - 单一职责原则 (SRP)
**检查要点:** **检查要点:**
- 这个类/模块是否只有一个改变的理由? - 这个类/模块是否只有一个改变的理由?
- 类中的方法是否都服务于同一个目的? - 类中的方法是否都服务于同一个目的?
- 如果要向非技术人员描述这个类,能否用一句话说清楚? - 如果要向非技术人员描述这个类,能否用一句话说清楚?
**代码审查中的识别信号:** **代码审查中的识别信号:**
``` ```
⚠️ 类名包含 "And"、"Manager"、"Handler"、"Processor" 等泛化词汇 ⚠️ 类名包含 "And"、"Manager"、"Handler"、"Processor" 等泛化词汇
⚠️ 一个类超过 200-300 行代码 ⚠️ 一个类超过 200-300 行代码
@@ -20,17 +22,20 @@
``` ```
**审查问题:** **审查问题:**
- "这个类负责哪些事情?能否拆分?" - "这个类负责哪些事情?能否拆分?"
- "如果 X 需求变化,哪些方法需要改?如果 Y 需求变化呢?" - "如果 X 需求变化,哪些方法需要改?如果 Y 需求变化呢?"
### O - 开闭原则 (OCP) ### O - 开闭原则 (OCP)
**检查要点:** **检查要点:**
- 添加新功能时,是否需要修改现有代码? - 添加新功能时,是否需要修改现有代码?
- 是否可以通过扩展(继承、组合)来添加新行为? - 是否可以通过扩展(继承、组合)来添加新行为?
- 是否存在大量的 if/else 或 switch 语句来处理不同类型? - 是否存在大量的 if/else 或 switch 语句来处理不同类型?
**代码审查中的识别信号:** **代码审查中的识别信号:**
``` ```
⚠️ switch/if-else 链处理不同类型 ⚠️ switch/if-else 链处理不同类型
⚠️ 添加新功能需要修改核心类 ⚠️ 添加新功能需要修改核心类
@@ -38,17 +43,20 @@
``` ```
**审查问题:** **审查问题:**
- "如果要添加新的 X 类型,需要修改哪些文件?" - "如果要添加新的 X 类型,需要修改哪些文件?"
- "这个 switch 语句会随着新类型增加而增长吗?" - "这个 switch 语句会随着新类型增加而增长吗?"
### L - 里氏替换原则 (LSP) ### L - 里氏替换原则 (LSP)
**检查要点:** **检查要点:**
- 子类是否可以完全替代父类使用? - 子类是否可以完全替代父类使用?
- 子类是否改变了父类方法的预期行为? - 子类是否改变了父类方法的预期行为?
- 是否存在子类抛出父类未声明的异常? - 是否存在子类抛出父类未声明的异常?
**代码审查中的识别信号:** **代码审查中的识别信号:**
``` ```
⚠️ 显式类型转换 (casting) ⚠️ 显式类型转换 (casting)
⚠️ 子类方法抛出 NotImplementedException ⚠️ 子类方法抛出 NotImplementedException
@@ -57,17 +65,20 @@
``` ```
**审查问题:** **审查问题:**
- "如果用子类替换父类,调用方代码是否需要修改?" - "如果用子类替换父类,调用方代码是否需要修改?"
- "这个方法在子类中的行为是否符合父类的契约?" - "这个方法在子类中的行为是否符合父类的契约?"
### I - 接口隔离原则 (ISP) ### I - 接口隔离原则 (ISP)
**检查要点:** **检查要点:**
- 接口是否足够小且专注? - 接口是否足够小且专注?
- 实现类是否被迫实现不需要的方法? - 实现类是否被迫实现不需要的方法?
- 客户端是否依赖了它不使用的方法? - 客户端是否依赖了它不使用的方法?
**代码审查中的识别信号:** **代码审查中的识别信号:**
``` ```
⚠️ 接口超过 5-7 个方法 ⚠️ 接口超过 5-7 个方法
⚠️ 实现类有空方法或抛出 NotImplementedException ⚠️ 实现类有空方法或抛出 NotImplementedException
@@ -76,17 +87,20 @@
``` ```
**审查问题:** **审查问题:**
- "这个接口的所有方法是否都被每个实现类使用?" - "这个接口的所有方法是否都被每个实现类使用?"
- "能否将这个大接口拆分为更小的专用接口?" - "能否将这个大接口拆分为更小的专用接口?"
### D - 依赖倒置原则 (DIP) ### D - 依赖倒置原则 (DIP)
**检查要点:** **检查要点:**
- 高层模块是否依赖于抽象而非具体实现? - 高层模块是否依赖于抽象而非具体实现?
- 是否使用依赖注入而非直接 new 对象? - 是否使用依赖注入而非直接 new 对象?
- 抽象是否由高层模块定义而非低层模块? - 抽象是否由高层模块定义而非低层模块?
**代码审查中的识别信号:** **代码审查中的识别信号:**
``` ```
⚠️ 高层模块直接 new 低层模块的具体类 ⚠️ 高层模块直接 new 低层模块的具体类
⚠️ 导入具体实现类而非接口/抽象类 ⚠️ 导入具体实现类而非接口/抽象类
@@ -95,6 +109,7 @@
``` ```
**审查问题:** **审查问题:**
- "这个类的依赖能否在测试时被 mock 替换?" - "这个类的依赖能否在测试时被 mock 替换?"
- "如果要更换数据库/API 实现,需要修改多少地方?" - "如果要更换数据库/API 实现,需要修改多少地方?"
@@ -104,21 +119,21 @@
### 致命反模式 ### 致命反模式
| 反模式 | 识别信号 | 影响 | | 反模式 | 识别信号 | 影响 |
|--------|----------|------| | ---------------------------- | -------------------------------------------------- | ---------------------- |
| **大泥球 (Big Ball of Mud)** | 没有清晰的模块边界,任何代码都可能调用任何其他代码 | 难以理解、修改和测试 | | **大泥球 (Big Ball of Mud)** | 没有清晰的模块边界,任何代码都可能调用任何其他代码 | 难以理解、修改和测试 |
| **上帝类 (God Object)** | 单个类承担过多职责,知道太多、做太多 | 高耦合,难以重用和测试 | | **上帝类 (God Object)** | 单个类承担过多职责,知道太多、做太多 | 高耦合,难以重用和测试 |
| **意大利面条代码** | 控制流程混乱,goto 或深层嵌套,难以追踪执行路径 | 难以理解和维护 | | **意大利面条代码** | 控制流程混乱,goto 或深层嵌套,难以追踪执行路径 | 难以理解和维护 |
| **熔岩流 (Lava Flow)** | 没人敢动的古老代码,缺乏文档和测试 | 技术债务累积 | | **熔岩流 (Lava Flow)** | 没人敢动的古老代码,缺乏文档和测试 | 技术债务累积 |
### 设计反模式 ### 设计反模式
| 反模式 | 识别信号 | 建议 | | 反模式 | 识别信号 | 建议 |
|--------|----------|------| | -------------------------- | ------------------------------------ | -------------------------- |
| **金锤子 (Golden Hammer)** | 对所有问题使用同一种技术/模式 | 根据问题选择合适的解决方案 | | **金锤子 (Golden Hammer)** | 对所有问题使用同一种技术/模式 | 根据问题选择合适的解决方案 |
| **过度工程 (Gas Factory)** | 简单问题用复杂方案解决,滥用设计模式 | YAGNI 原则,先简单后复杂 | | **过度工程 (Gas Factory)** | 简单问题用复杂方案解决,滥用设计模式 | YAGNI 原则,先简单后复杂 |
| **船锚 (Boat Anchor)** | 为"将来可能需要"而写的未使用代码 | 删除未使用代码,需要时再写 | | **船锚 (Boat Anchor)** | 为"将来可能需要"而写的未使用代码 | 删除未使用代码,需要时再写 |
| **复制粘贴编程** | 相同逻辑出现在多处 | 提取公共方法或模块 | | **复制粘贴编程** | 相同逻辑出现在多处 | 提取公共方法或模块 |
### 审查问题 ### 审查问题
@@ -134,25 +149,25 @@
### 耦合类型(从好到差) ### 耦合类型(从好到差)
| 类型 | 描述 | 示例 | | 类型 | 描述 | 示例 |
|------|------|------| | --------------- | -------------------------- | ----------------------------- |
| **消息耦合** ✅ | 通过参数传递数据 | `calculate(price, quantity)` | | **消息耦合** ✅ | 通过参数传递数据 | `calculate(price, quantity)` |
| **数据耦合** ✅ | 共享简单数据结构 | `processOrder(orderDTO)` | | **数据耦合** ✅ | 共享简单数据结构 | `processOrder(orderDTO)` |
| **印记耦合** ⚠️ | 共享复杂数据结构但只用部分 | 传入整个 User 对象但只用 name | | **印记耦合** ⚠️ | 共享复杂数据结构但只用部分 | 传入整个 User 对象但只用 name |
| **控制耦合** ⚠️ | 传递控制标志影响行为 | `process(data, isAdmin=true)` | | **控制耦合** ⚠️ | 传递控制标志影响行为 | `process(data, isAdmin=true)` |
| **公共耦合** ❌ | 共享全局变量 | 多个模块读写同一个全局状态 | | **公共耦合** ❌ | 共享全局变量 | 多个模块读写同一个全局状态 |
| **内容耦合** ❌ | 直接访问另一模块的内部 | 直接操作另一个类的私有属性 | | **内容耦合** ❌ | 直接访问另一模块的内部 | 直接操作另一个类的私有属性 |
### 内聚类型(从好到差) ### 内聚类型(从好到差)
| 类型 | 描述 | 质量 | | 类型 | 描述 | 质量 |
|------|------|------| | ------------ | -------------------- | --------- |
| **功能内聚** | 所有元素完成单一任务 | ✅ 最佳 | | **功能内聚** | 所有元素完成单一任务 | ✅ 最佳 |
| **顺序内聚** | 输出作为下一步输入 | ✅ 良好 | | **顺序内聚** | 输出作为下一步输入 | ✅ 良好 |
| **通信内聚** | 操作相同数据 | ⚠️ 可接受 | | **通信内聚** | 操作相同数据 | ⚠️ 可接受 |
| **时间内聚** | 同时执行的任务 | ⚠️ 较差 | | **时间内聚** | 同时执行的任务 | ⚠️ 较差 |
| **逻辑内聚** | 逻辑相关但功能不同 | ❌ 差 | | **逻辑内聚** | 逻辑相关但功能不同 | ❌ 差 |
| **偶然内聚** | 没有明显关系 | ❌ 最差 | | **偶然内聚** | 没有明显关系 | ❌ 最差 |
### 度量指标参考 ### 度量指标参考
@@ -220,19 +235,23 @@ interface UserRepository {
// infrastructure/MySQLUserRepository.ts (实现) // infrastructure/MySQLUserRepository.ts (实现)
class MySQLUserRepository implements UserRepository { class MySQLUserRepository implements UserRepository {
findById(id: string): Promise<User> { /* ... */ } findById(id: string): Promise<User> {
/* ... */
}
} }
``` ```
### 审查清单 ### 审查清单
**层次边界检查:** **层次边界检查:**
- [ ] Domain 层是否有外部依赖(数据库、HTTP、文件系统)? - [ ] Domain 层是否有外部依赖(数据库、HTTP、文件系统)?
- [ ] Application 层是否直接操作数据库或调用外部 API? - [ ] Application 层是否直接操作数据库或调用外部 API?
- [ ] Controller 是否包含业务逻辑? - [ ] Controller 是否包含业务逻辑?
- [ ] 是否存在跨层调用(UI 直接调用 Repository)? - [ ] 是否存在跨层调用(UI 直接调用 Repository)?
**关注点分离检查:** **关注点分离检查:**
- [ ] 业务逻辑是否与展示逻辑分离? - [ ] 业务逻辑是否与展示逻辑分离?
- [ ] 数据访问是否封装在专门的层? - [ ] 数据访问是否封装在专门的层?
- [ ] 配置和环境相关代码是否集中管理? - [ ] 配置和环境相关代码是否集中管理?
@@ -251,13 +270,13 @@ class MySQLUserRepository implements UserRepository {
### 何时使用设计模式 ### 何时使用设计模式
| 模式 | 适用场景 | 不适用场景 | | 模式 | 适用场景 | 不适用场景 |
|------|----------|------------| | ------------- | ---------------------------------------- | ---------------------------- |
| **Factory** | 需要创建不同类型对象,类型在运行时确定 | 只有一种类型,或类型固定不变 | | **Factory** | 需要创建不同类型对象,类型在运行时确定 | 只有一种类型,或类型固定不变 |
| **Strategy** | 算法需要在运行时切换,有多种可互换的行为 | 只有一种算法,或算法不会变化 | | **Strategy** | 算法需要在运行时切换,有多种可互换的行为 | 只有一种算法,或算法不会变化 |
| **Observer** | 一对多依赖,状态变化需要通知多个对象 | 简单的直接调用即可满足需求 | | **Observer** | 一对多依赖,状态变化需要通知多个对象 | 简单的直接调用即可满足需求 |
| **Singleton** | 确实需要全局唯一实例,如配置管理 | 可以通过依赖注入传递的对象 | | **Singleton** | 确实需要全局唯一实例,如配置管理 | 可以通过依赖注入传递的对象 |
| **Decorator** | 需要动态添加职责,避免继承爆炸 | 职责固定,不需要动态组合 | | **Decorator** | 需要动态添加职责,避免继承爆炸 | 职责固定,不需要动态组合 |
### 过度设计警告信号 ### 过度设计警告信号
@@ -275,11 +294,13 @@ class MySQLUserRepository implements UserRepository {
```markdown ```markdown
✅ 正确使用模式: ✅ 正确使用模式:
- 解决了实际的可扩展性问题 - 解决了实际的可扩展性问题
- 代码更容易理解和测试 - 代码更容易理解和测试
- 添加新功能变得更简单 - 添加新功能变得更简单
❌ 过度使用模式: ❌ 过度使用模式:
- 为了使用模式而使用 - 为了使用模式而使用
- 增加了不必要的复杂度 - 增加了不必要的复杂度
- 违反了 YAGNI 原则 - 违反了 YAGNI 原则
@@ -298,16 +319,19 @@ class MySQLUserRepository implements UserRepository {
### 扩展性检查清单 ### 扩展性检查清单
**功能扩展性:** **功能扩展性:**
- [ ] 添加新功能是否需要修改核心代码? - [ ] 添加新功能是否需要修改核心代码?
- [ ] 是否提供了扩展点(hooks、plugins、events)? - [ ] 是否提供了扩展点(hooks、plugins、events)?
- [ ] 配置是否外部化(配置文件、环境变量)? - [ ] 配置是否外部化(配置文件、环境变量)?
**数据扩展性:** **数据扩展性:**
- [ ] 数据模型是否支持新增字段? - [ ] 数据模型是否支持新增字段?
- [ ] 是否考虑了数据量增长的场景? - [ ] 是否考虑了数据量增长的场景?
- [ ] 查询是否有合适的索引? - [ ] 查询是否有合适的索引?
**负载扩展性:** **负载扩展性:**
- [ ] 是否可以水平扩展(添加更多实例)? - [ ] 是否可以水平扩展(添加更多实例)?
- [ ] 是否有状态依赖(session、本地缓存)? - [ ] 是否有状态依赖(session、本地缓存)?
- [ ] 数据库连接是否使用连接池? - [ ] 数据库连接是否使用连接池?
@@ -330,9 +354,9 @@ class OrderService {
// ❌ 差的扩展设计:硬编码所有行为 // ❌ 差的扩展设计:硬编码所有行为
class OrderService { class OrderService {
async createOrder(order: Order) { async createOrder(order: Order) {
await this.sendEmail(order); // 硬编码 await this.sendEmail(order); // 硬编码
await this.updateInventory(order); // 硬编码 await this.updateInventory(order); // 硬编码
await this.notifyWarehouse(order); // 硬编码 await this.notifyWarehouse(order); // 硬编码
return await this.save(order); return await this.save(order);
} }
} }
@@ -353,6 +377,7 @@ class OrderService {
### 目录组织 ### 目录组织
**按功能/领域组织(推荐):** **按功能/领域组织(推荐):**
``` ```
src/ src/
├── user/ ├── user/
@@ -370,6 +395,7 @@ src/
``` ```
**按技术层组织(不推荐):** **按技术层组织(不推荐):**
``` ```
src/ src/
├── controllers/ ← 不同领域混在一起 ├── controllers/ ← 不同领域混在一起
@@ -382,13 +408,13 @@ src/
### 命名约定检查 ### 命名约定检查
| 类型 | 约定 | 示例 | | 类型 | 约定 | 示例 |
|------|------|------| | -------- | ---------------- | -------------------------------- |
| 类名 | PascalCase,名词 | `UserService`, `OrderRepository` | | 类名 | PascalCase,名词 | `UserService`, `OrderRepository` |
| 方法名 | camelCase,动词 | `createUser`, `findOrderById` | | 方法名 | camelCase,动词 | `createUser`, `findOrderById` |
| 接口名 | I 前缀或无前缀 | `IUserService``UserService` | | 接口名 | I 前缀或无前缀 | `IUserService``UserService` |
| 常量 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` | | 常量 | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
| 私有属性 | 下划线前缀或无 | `_cache``#cache` | | 私有属性 | 下划线前缀或无 | `_cache``#cache` |
### 文件大小指南 ### 文件大小指南
@@ -452,14 +478,14 @@ src/
## 工具推荐 ## 工具推荐
| 工具 | 用途 | 语言支持 | | 工具 | 用途 | 语言支持 |
|------|------|----------| | ------------- | -------------------- | --------------------- |
| **SonarQube** | 代码质量、耦合度分析 | 多语言 | | **SonarQube** | 代码质量、耦合度分析 | 多语言 |
| **NDepend** | 依赖分析、架构规则 | .NET | | **NDepend** | 依赖分析、架构规则 | .NET |
| **JDepend** | 包依赖分析 | Java | | **JDepend** | 包依赖分析 | Java |
| **Madge** | 模块依赖图 | JavaScript/TypeScript | | **Madge** | 模块依赖图 | JavaScript/TypeScript |
| **ESLint** | 代码规范、复杂度检查 | JavaScript/TypeScript | | **ESLint** | 代码规范、复杂度检查 | JavaScript/TypeScript |
| **CodeScene** | 技术债务、热点分析 | 多语言 | | **CodeScene** | 技术债务、热点分析 | 多语言 |
--- ---
@@ -264,22 +264,26 @@ clang-format -i src/*.c include/*.h
## Review Checklist ## Review Checklist
### Memory and UB ### Memory and UB
- [ ] All buffers have explicit size parameters - [ ] All buffers have explicit size parameters
- [ ] No out-of-bounds access or pointer arithmetic past objects - [ ] No out-of-bounds access or pointer arithmetic past objects
- [ ] No use after free or uninitialized reads - [ ] No use after free or uninitialized reads
- [ ] Signed overflow and shift rules are respected - [ ] Signed overflow and shift rules are respected
### API and Design ### API and Design
- [ ] Ownership rules are documented and consistent - [ ] Ownership rules are documented and consistent
- [ ] const-correctness is applied for inputs - [ ] const-correctness is applied for inputs
- [ ] Error contracts are clear and consistent - [ ] Error contracts are clear and consistent
### Concurrency ### Concurrency
- [ ] No data races on shared state - [ ] No data races on shared state
- [ ] volatile is not used for synchronization - [ ] volatile is not used for synchronization
- [ ] Locks are held for minimal time - [ ] Locks are held for minimal time
### Tooling and Tests ### Tooling and Tests
- [ ] Builds clean with warnings enabled - [ ] Builds clean with warnings enabled
- [ ] Sanitizers run on critical code paths - [ ] Sanitizers run on critical code paths
- [ ] Static analysis results are addressed - [ ] Static analysis results are addressed
@@ -7,6 +7,7 @@ Comprehensive guidelines for conducting effective code reviews.
### Goals of Code Review ### Goals of Code Review
**Primary Goals:** **Primary Goals:**
- Catch bugs and edge cases before production - Catch bugs and edge cases before production
- Ensure code maintainability and readability - Ensure code maintainability and readability
- Share knowledge across the team - Share knowledge across the team
@@ -14,6 +15,7 @@ Comprehensive guidelines for conducting effective code reviews.
- Improve design and architecture decisions - Improve design and architecture decisions
**Secondary Goals:** **Secondary Goals:**
- Mentor junior developers - Mentor junior developers
- Build team culture and trust - Build team culture and trust
- Document design decisions through discussions - Document design decisions through discussions
@@ -29,11 +31,11 @@ Comprehensive guidelines for conducting effective code reviews.
### When to Review ### When to Review
| Trigger | Action | | Trigger | Action |
|---------|--------| | -------------------- | ---------------------------------------- |
| PR opened | Review within 24 hours, ideally same day | | PR opened | Review within 24 hours, ideally same day |
| Changes requested | Re-review within 4 hours | | Changes requested | Re-review within 4 hours |
| Blocking issue found | Communicate immediately | | Blocking issue found | Communicate immediately |
### Time Allocation ### Time Allocation
@@ -44,18 +46,21 @@ Comprehensive guidelines for conducting effective code reviews.
## Review Depth Levels ## Review Depth Levels
### Level 1: Skim Review (5 minutes) ### Level 1: Skim Review (5 minutes)
- Check PR description and linked issues - Check PR description and linked issues
- Verify CI/CD status - Verify CI/CD status
- Look at file changes overview - Look at file changes overview
- Identify if deeper review needed - Identify if deeper review needed
### Level 2: Standard Review (20-30 minutes) ### Level 2: Standard Review (20-30 minutes)
- Full code walkthrough - Full code walkthrough
- Logic verification - Logic verification
- Test coverage check - Test coverage check
- Security scan - Security scan
### Level 3: Deep Review (60+ minutes) ### Level 3: Deep Review (60+ minutes)
- Architecture evaluation - Architecture evaluation
- Performance analysis - Performance analysis
- Security audit - Security audit
@@ -66,11 +71,13 @@ Comprehensive guidelines for conducting effective code reviews.
### Tone and Language ### Tone and Language
**Use collaborative language:** **Use collaborative language:**
- "What do you think about..." instead of "You should..." - "What do you think about..." instead of "You should..."
- "Could we consider..." instead of "This is wrong" - "Could we consider..." instead of "This is wrong"
- "I'm curious about..." instead of "Why didn't you..." - "I'm curious about..." instead of "Why didn't you..."
**Be specific and actionable:** **Be specific and actionable:**
- Include code examples when suggesting changes - Include code examples when suggesting changes
- Link to documentation or past discussions - Link to documentation or past discussions
- Explain the "why" behind suggestions - Explain the "why" behind suggestions
@@ -86,6 +93,7 @@ Comprehensive guidelines for conducting effective code reviews.
## Review Prioritization ## Review Prioritization
### Must Fix (Blocking) ### Must Fix (Blocking)
- Security vulnerabilities - Security vulnerabilities
- Data corruption risks - Data corruption risks
- Breaking changes without migration - Breaking changes without migration
@@ -93,6 +101,7 @@ Comprehensive guidelines for conducting effective code reviews.
- Missing error handling for user-facing features - Missing error handling for user-facing features
### Should Fix (Important) ### Should Fix (Important)
- Test coverage gaps - Test coverage gaps
- Moderate performance concerns - Moderate performance concerns
- Code duplication - Code duplication
@@ -100,6 +109,7 @@ Comprehensive guidelines for conducting effective code reviews.
- Missing documentation for complex logic - Missing documentation for complex logic
### Nice to Have (Non-blocking) ### Nice to Have (Non-blocking)
- Style preferences beyond linting - Style preferences beyond linting
- Minor optimizations - Minor optimizations
- Additional test cases - Additional test cases
@@ -108,6 +118,7 @@ Comprehensive guidelines for conducting effective code reviews.
## Anti-Patterns to Avoid ## Anti-Patterns to Avoid
### Reviewer Anti-Patterns ### Reviewer Anti-Patterns
- **Rubber stamping**: Approving without actually reviewing - **Rubber stamping**: Approving without actually reviewing
- **Bike shedding**: Debating trivial details extensively - **Bike shedding**: Debating trivial details extensively
- **Scope creep**: "While you're at it, can you also..." - **Scope creep**: "While you're at it, can you also..."
@@ -115,6 +126,7 @@ Comprehensive guidelines for conducting effective code reviews.
- **Perfectionism**: Blocking for minor style preferences - **Perfectionism**: Blocking for minor style preferences
### Author Anti-Patterns ### Author Anti-Patterns
- **Mega PRs**: Submitting 1000+ line changes - **Mega PRs**: Submitting 1000+ line changes
- **No context**: Missing PR description or linked issues - **No context**: Missing PR description or linked issues
- **Defensive responses**: Arguing every suggestion - **Defensive responses**: Arguing every suggestion
@@ -123,6 +135,7 @@ Comprehensive guidelines for conducting effective code reviews.
## Metrics and Improvement ## Metrics and Improvement
### Track These Metrics ### Track These Metrics
- Time to first review - Time to first review
- Review cycle time - Review cycle time
- Number of review rounds - Number of review rounds
@@ -130,6 +143,7 @@ Comprehensive guidelines for conducting effective code reviews.
- Review coverage percentage - Review coverage percentage
### Continuous Improvement ### Continuous Improvement
- Hold retrospectives on review process - Hold retrospectives on review process
- Share learnings from escaped bugs - Share learnings from escaped bugs
- Update checklists based on common issues - Update checklists based on common issues
@@ -5,6 +5,7 @@ Language-specific bugs and issues to watch for during code review.
## Universal Issues ## Universal Issues
### Logic Errors ### Logic Errors
- [ ] Off-by-one errors in loops and array access - [ ] Off-by-one errors in loops and array access
- [ ] Incorrect boolean logic (De Morgan's law violations) - [ ] Incorrect boolean logic (De Morgan's law violations)
- [ ] Missing null/undefined checks - [ ] Missing null/undefined checks
@@ -14,6 +15,7 @@ Language-specific bugs and issues to watch for during code review.
- [ ] Floating point comparison issues - [ ] Floating point comparison issues
### Resource Management ### Resource Management
- [ ] Memory leaks (unclosed connections, listeners) - [ ] Memory leaks (unclosed connections, listeners)
- [ ] File handles not closed - [ ] File handles not closed
- [ ] Database connections not released - [ ] Database connections not released
@@ -21,6 +23,7 @@ Language-specific bugs and issues to watch for during code review.
- [ ] Timers/intervals not cleared - [ ] Timers/intervals not cleared
### Error Handling ### Error Handling
- [ ] Swallowed exceptions (empty catch blocks) - [ ] Swallowed exceptions (empty catch blocks)
- [ ] Generic exception handling hiding specific errors - [ ] Generic exception handling hiding specific errors
- [ ] Missing error propagation - [ ] Missing error propagation
@@ -30,26 +33,34 @@ Language-specific bugs and issues to watch for during code review.
## TypeScript/JavaScript ## TypeScript/JavaScript
### Type Issues ### Type Issues
```typescript ```typescript
// ❌ Using any defeats type safety // ❌ Using any defeats type safety
function process(data: any) { return data.value; } function process(data: any) {
return data.value;
}
// ✅ Use proper types // ✅ Use proper types
interface Data { value: string; } interface Data {
function process(data: Data) { return data.value; } value: string;
}
function process(data: Data) {
return data.value;
}
``` ```
### Async/Await Pitfalls ### Async/Await Pitfalls
```typescript ```typescript
// ❌ Missing await // ❌ Missing await
async function fetch() { async function fetch() {
const data = fetchData(); // Missing await! const data = fetchData(); // Missing await!
return data.json(); return data.json();
} }
// ❌ Unhandled promise rejection // ❌ Unhandled promise rejection
async function risky() { async function risky() {
const result = await fetchData(); // No try-catch const result = await fetchData(); // No try-catch
return result; return result;
} }
@@ -68,11 +79,12 @@ async function safe() {
### React Specific ### React Specific
#### Hooks 规则违反 #### Hooks 规则违反
```tsx ```tsx
// ❌ 条件调用 Hooks — 违反 Hooks 规则 // ❌ 条件调用 Hooks — 违反 Hooks 规则
function BadComponent({ show }) { function BadComponent({ show }) {
if (show) { if (show) {
const [value, setValue] = useState(0); // Error! const [value, setValue] = useState(0); // Error!
} }
return <div>...</div>; return <div>...</div>;
} }
@@ -86,36 +98,35 @@ function GoodComponent({ show }) {
// ❌ 循环中调用 Hooks // ❌ 循环中调用 Hooks
function BadLoop({ items }) { function BadLoop({ items }) {
items.forEach(item => { items.forEach((item) => {
const [selected, setSelected] = useState(false); // Error! const [selected, setSelected] = useState(false); // Error!
}); });
} }
// ✅ 将状态提升或使用不同的数据结构 // ✅ 将状态提升或使用不同的数据结构
function GoodLoop({ items }) { function GoodLoop({ items }) {
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
return items.map(item => ( return items.map((item) => <Item key={item.id} selected={selectedIds.has(item.id)} />);
<Item key={item.id} selected={selectedIds.has(item.id)} />
));
} }
``` ```
#### useEffect 常见错误 #### useEffect 常见错误
```tsx ```tsx
// ❌ 依赖数组不完整 — stale closure // ❌ 依赖数组不完整 — stale closure
function StaleClosureExample({ userId, onSuccess }) { function StaleClosureExample({ userId, onSuccess }) {
const [data, setData] = useState(null); const [data, setData] = useState(null);
useEffect(() => { useEffect(() => {
fetchData(userId).then(result => { fetchData(userId).then((result) => {
setData(result); setData(result);
onSuccess(result); // onSuccess 可能是 stale 的! onSuccess(result); // onSuccess 可能是 stale 的!
}); });
}, [userId]); // 缺少 onSuccess 依赖 }, [userId]); // 缺少 onSuccess 依赖
} }
// ✅ 完整的依赖数组 // ✅ 完整的依赖数组
useEffect(() => { useEffect(() => {
fetchData(userId).then(result => { fetchData(userId).then((result) => {
setData(result); setData(result);
onSuccess(result); onSuccess(result);
}); });
@@ -125,15 +136,15 @@ useEffect(() => {
function InfiniteLoop() { function InfiniteLoop() {
const [count, setCount] = useState(0); const [count, setCount] = useState(0);
useEffect(() => { useEffect(() => {
setCount(count + 1); // 触发重渲染,又触发 effect setCount(count + 1); // 触发重渲染,又触发 effect
}, [count]); // 无限循环! }, [count]); // 无限循环!
} }
// ❌ 缺少清理函数 — 内存泄漏 // ❌ 缺少清理函数 — 内存泄漏
function MemoryLeak({ userId }) { function MemoryLeak({ userId }) {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
fetchUser(userId).then(setUser); // 组件卸载后仍然调用 setUser fetchUser(userId).then(setUser); // 组件卸载后仍然调用 setUser
}, [userId]); }, [userId]);
} }
@@ -142,10 +153,12 @@ function NoLeak({ userId }) {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
fetchUser(userId).then(data => { fetchUser(userId).then((data) => {
if (!cancelled) setUser(data); if (!cancelled) setUser(data);
}); });
return () => { cancelled = true; }; return () => {
cancelled = true;
};
}, [userId]); }, [userId]);
} }
@@ -154,22 +167,19 @@ function BadDerived({ items }) {
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
useEffect(() => { useEffect(() => {
setTotal(items.reduce((a, b) => a + b.price, 0)); setTotal(items.reduce((a, b) => a + b.price, 0));
}, [items]); // 不必要的 effect + 额外渲染 }, [items]); // 不必要的 effect + 额外渲染
} }
// ✅ 直接计算或用 useMemo // ✅ 直接计算或用 useMemo
function GoodDerived({ items }) { function GoodDerived({ items }) {
const total = useMemo( const total = useMemo(() => items.reduce((a, b) => a + b.price, 0), [items]);
() => items.reduce((a, b) => a + b.price, 0),
[items]
);
} }
// ❌ useEffect 用于事件响应 // ❌ useEffect 用于事件响应
function BadEvent() { function BadEvent() {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
useEffect(() => { useEffect(() => {
if (query) logSearch(query); // 应该在事件处理器中 if (query) logSearch(query); // 应该在事件处理器中
}, [query]); }, [query]);
} }
@@ -183,11 +193,12 @@ function GoodEvent() {
``` ```
#### useMemo / useCallback 误用 #### useMemo / useCallback 误用
```tsx ```tsx
// ❌ 过度优化 — 常量不需要 memo // ❌ 过度优化 — 常量不需要 memo
function OverOptimized() { function OverOptimized() {
const config = useMemo(() => ({ api: '/v1' }), []); // 无意义 const config = useMemo(() => ({ api: '/v1' }), []); // 无意义
const noop = useCallback(() => {}, []); // 无意义 const noop = useCallback(() => {}, []); // 无意义
} }
// ❌ 空依赖的 useMemo(可能隐藏 bug // ❌ 空依赖的 useMemo(可能隐藏 bug
@@ -200,7 +211,7 @@ function EmptyDeps({ user }) {
function UselessCallback({ data }) { function UselessCallback({ data }) {
const process = useCallback(() => { const process = useCallback(() => {
return data.map(transform); return data.map(transform);
}, [data]); // 如果 data 每次都是新引用,完全无效 }, [data]); // 如果 data 每次都是新引用,完全无效
} }
// ❌ useMemo/useCallback 没有配合 React.memo // ❌ useMemo/useCallback 没有配合 React.memo
@@ -224,6 +235,7 @@ function Parent() {
``` ```
#### 组件设计问题 #### 组件设计问题
```tsx ```tsx
// ❌ 在组件内定义组件 // ❌ 在组件内定义组件
function Parent() { function Parent() {
@@ -242,21 +254,22 @@ function Parent() {
function BadProps() { function BadProps() {
return ( return (
<MemoComponent <MemoComponent
style={{ color: 'red' }} // 每次渲染新对象 style={{ color: 'red' }} // 每次渲染新对象
onClick={() => handle()} // 每次渲染新函数 onClick={() => handle()} // 每次渲染新函数
items={data.filter(x => x)} // 每次渲染新数组 items={data.filter((x) => x)} // 每次渲染新数组
/> />
); );
} }
// ❌ 直接修改 props // ❌ 直接修改 props
function MutateProps({ user }) { function MutateProps({ user }) {
user.name = 'Changed'; // 永远不要这样做! user.name = 'Changed'; // 永远不要这样做!
return <div>{user.name}</div>; return <div>{user.name}</div>;
} }
``` ```
#### Server Components 错误 (React 19+) #### Server Components 错误 (React 19+)
```tsx ```tsx
// ❌ 在 Server Component 中使用客户端 API // ❌ 在 Server Component 中使用客户端 API
// app/page.tsx (默认是 Server Component) // app/page.tsx (默认是 Server Component)
@@ -288,10 +301,11 @@ export default function Layout({ children }) { ... }
``` ```
#### 测试常见错误 #### 测试常见错误
```tsx ```tsx
// ❌ 使用 container 查询 // ❌ 使用 container 查询
const { container } = render(<Component />); const { container } = render(<Component />);
const button = container.querySelector('button'); // 不推荐 const button = container.querySelector('button'); // 不推荐
// ✅ 使用 screen 和语义查询 // ✅ 使用 screen 和语义查询
render(<Component />); render(<Component />);
@@ -310,13 +324,14 @@ expect(component.state.isOpen).toBe(true);
expect(screen.getByRole('dialog')).toBeVisible(); expect(screen.getByRole('dialog')).toBeVisible();
// ❌ 等待同步查询 // ❌ 等待同步查询
await screen.getByText('Hello'); // getBy 是同步的 await screen.getByText('Hello'); // getBy 是同步的
// ✅ 异步用 findBy // ✅ 异步用 findBy
await screen.findByText('Hello'); // findBy 会等待 await screen.findByText('Hello'); // findBy 会等待
``` ```
### React Common Mistakes Checklist ### React Common Mistakes Checklist
- [ ] Hooks 不在顶层调用(条件/循环中) - [ ] Hooks 不在顶层调用(条件/循环中)
- [ ] useEffect 依赖数组不完整 - [ ] useEffect 依赖数组不完整
- [ ] useEffect 缺少清理函数 - [ ] useEffect 缺少清理函数
@@ -339,18 +354,18 @@ await screen.findByText('Hello'); // findBy 会等待
// ❌ 在 Action 中直接 setState 而不是返回状态 // ❌ 在 Action 中直接 setState 而不是返回状态
const [state, action] = useActionState(async (prev, formData) => { const [state, action] = useActionState(async (prev, formData) => {
setSomeState(newValue); // 错误!应该返回新状态 setSomeState(newValue); // 错误!应该返回新状态
}, initialState); }, initialState);
// ✅ 返回新状态 // ✅ 返回新状态
const [state, action] = useActionState(async (prev, formData) => { const [state, action] = useActionState(async (prev, formData) => {
const result = await submitForm(formData); const result = await submitForm(formData);
return { ...prev, data: result }; // 返回新状态 return { ...prev, data: result }; // 返回新状态
}, initialState); }, initialState);
// ❌ 忘记处理 isPending // ❌ 忘记处理 isPending
const [state, action] = useActionState(submitAction, null); const [state, action] = useActionState(submitAction, null);
return <button>Submit</button>; // 用户可以重复点击 return <button>Submit</button>; // 用户可以重复点击
// ✅ 使用 isPending 禁用按钮 // ✅ 使用 isPending 禁用按钮
const [state, action, isPending] = useActionState(submitAction, null); const [state, action, isPending] = useActionState(submitAction, null);
@@ -360,8 +375,12 @@ return <button disabled={isPending}>Submit</button>;
// ❌ 在 form 同级调用 useFormStatus // ❌ 在 form 同级调用 useFormStatus
function Form() { function Form() {
const { pending } = useFormStatus(); // 永远是 undefined const { pending } = useFormStatus(); // 永远是 undefined
return <form><button disabled={pending}>Submit</button></form>; return (
<form>
<button disabled={pending}>Submit</button>
</form>
);
} }
// ✅ 在子组件中调用 // ✅ 在子组件中调用
@@ -370,7 +389,11 @@ function SubmitButton() {
return <button disabled={pending}>Submit</button>; return <button disabled={pending}>Submit</button>;
} }
function Form() { function Form() {
return <form><SubmitButton /></form>; return (
<form>
<SubmitButton />
</form>
);
} }
// === useOptimistic 错误 === // === useOptimistic 错误 ===
@@ -379,7 +402,7 @@ function Form() {
function PaymentButton() { function PaymentButton() {
const [optimisticPaid, setPaid] = useOptimistic(false); const [optimisticPaid, setPaid] = useOptimistic(false);
const handlePay = async () => { const handlePay = async () => {
setPaid(true); // 危险:显示已支付但可能失败 setPaid(true); // 危险:显示已支付但可能失败
await processPayment(); await processPayment();
}; };
} }
@@ -394,12 +417,13 @@ const handleLike = async () => {
try { try {
await likePost(); await likePost();
} catch { } catch {
toast.error('点赞失败,请重试'); // 通知用户 toast.error('点赞失败,请重试'); // 通知用户
} }
}; };
``` ```
### React 19 Forms Checklist ### React 19 Forms Checklist
- [ ] useActionState 返回新状态而不是 setState - [ ] useActionState 返回新状态而不是 setState
- [ ] useActionState 正确使用 isPending 禁用提交 - [ ] useActionState 正确使用 isPending 禁用提交
- [ ] useFormStatus 在 form 子组件中调用 - [ ] useFormStatus 在 form 子组件中调用
@@ -416,9 +440,9 @@ const handleLike = async () => {
function BadPage() { function BadPage() {
return ( return (
<Suspense fallback={<FullPageLoader />}> <Suspense fallback={<FullPageLoader />}>
<FastHeader /> {/* 快 */} <FastHeader /> {/* 快 */}
<SlowMainContent /> {/* 慢——阻塞整个页面 */} <SlowMainContent /> {/* 慢——阻塞整个页面 */}
<FastFooter /> {/* 快 */} <FastFooter /> {/* 快 */}
</Suspense> </Suspense>
); );
} }
@@ -440,7 +464,7 @@ function GoodPage() {
function NoErrorHandling() { function NoErrorHandling() {
return ( return (
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<DataFetcher /> {/* 抛错导致白屏 */} <DataFetcher /> {/* 抛错导致白屏 */}
</Suspense> </Suspense>
); );
} }
@@ -460,7 +484,7 @@ function WithErrorHandling() {
// ❌ 在组件外创建 Promise(每次渲染新 Promise // ❌ 在组件外创建 Promise(每次渲染新 Promise
function BadUse() { function BadUse() {
const data = use(fetchData()); // 每次渲染都创建新 Promise const data = use(fetchData()); // 每次渲染都创建新 Promise
return <div>{data}</div>; return <div>{data}</div>;
} }
@@ -479,7 +503,7 @@ function Child({ dataPromise }) {
// ❌ 在 layout.tsx 中 await 慢数据——阻塞所有子页面 // ❌ 在 layout.tsx 中 await 慢数据——阻塞所有子页面
// app/layout.tsx // app/layout.tsx
export default async function Layout({ children }) { export default async function Layout({ children }) {
const config = await fetchSlowConfig(); // 阻塞整个应用! const config = await fetchSlowConfig(); // 阻塞整个应用!
return <ConfigProvider value={config}>{children}</ConfigProvider>; return <ConfigProvider value={config}>{children}</ConfigProvider>;
} }
@@ -495,6 +519,7 @@ export default function Layout({ children }) {
``` ```
### Suspense Checklist ### Suspense Checklist
- [ ] 慢内容有独立的 Suspense 边界 - [ ] 慢内容有独立的 Suspense 边界
- [ ] 每个 Suspense 有对应的 Error Boundary - [ ] 每个 Suspense 有对应的 Error Boundary
- [ ] fallback 是有意义的骨架屏(不是简单 spinner) - [ ] fallback 是有意义的骨架屏(不是简单 spinner)
@@ -510,7 +535,7 @@ export default function Layout({ children }) {
// ❌ queryKey 不包含查询参数 // ❌ queryKey 不包含查询参数
function BadQuery({ userId, filters }) { function BadQuery({ userId, filters }) {
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['users'], // 缺少 userId 和 filters queryKey: ['users'], // 缺少 userId 和 filters
queryFn: () => fetchUsers(userId, filters), queryFn: () => fetchUsers(userId, filters),
}); });
// userId 或 filters 变化时数据不会更新 // userId 或 filters 变化时数据不会更新
@@ -535,7 +560,7 @@ const { data } = useQuery({
const { data } = useQuery({ const { data } = useQuery({
queryKey: ['data'], queryKey: ['data'],
queryFn: fetchData, queryFn: fetchData,
staleTime: 5 * 60 * 1000, // 5 分钟内不会自动 refetch staleTime: 5 * 60 * 1000, // 5 分钟内不会自动 refetch
}); });
// === useSuspenseQuery 错误 === // === useSuspenseQuery 错误 ===
@@ -544,7 +569,7 @@ const { data } = useQuery({
const { data } = useSuspenseQuery({ const { data } = useSuspenseQuery({
queryKey: ['user', userId], queryKey: ['user', userId],
queryFn: () => fetchUser(userId), queryFn: () => fetchUser(userId),
enabled: !!userId, // 错误!useSuspenseQuery 不支持 enabled enabled: !!userId, // 错误!useSuspenseQuery 不支持 enabled
}); });
// ✅ 条件渲染实现 // ✅ 条件渲染实现
@@ -610,7 +635,7 @@ const mutation = useMutation({
// === v5 迁移错误 === // === v5 迁移错误 ===
// ❌ 使用废弃的 API // ❌ 使用废弃的 API
const { data, isLoading } = useQuery(['key'], fetchFn); // v4 语法 const { data, isLoading } = useQuery(['key'], fetchFn); // v4 语法
// ✅ v5 单一对象参数 // ✅ v5 单一对象参数
const { data, isPending } = useQuery({ const { data, isPending } = useQuery({
@@ -623,12 +648,13 @@ if (isLoading) return <Spinner />;
// v5 中 isLoading = isPending && isFetching // v5 中 isLoading = isPending && isFetching
// ✅ 根据意图选择 // ✅ 根据意图选择
if (isPending) return <Spinner />; // 没有缓存数据 if (isPending) return <Spinner />; // 没有缓存数据
// 或 // 或
if (isFetching) return <Refreshing />; // 正在后台刷新 if (isFetching) return <Refreshing />; // 正在后台刷新
``` ```
### TanStack Query Checklist ### TanStack Query Checklist
- [ ] queryKey 包含所有影响数据的参数 - [ ] queryKey 包含所有影响数据的参数
- [ ] 设置了合理的 staleTime(不是默认 0 - [ ] 设置了合理的 staleTime(不是默认 0
- [ ] useSuspenseQuery 不使用 enabled - [ ] useSuspenseQuery 不使用 enabled
@@ -638,6 +664,7 @@ if (isFetching) return <Refreshing />; // 正在后台刷新
- [ ] 理解 isPending vs isLoading vs isFetching - [ ] 理解 isPending vs isLoading vs isFetching
### TypeScript/JavaScript Common Mistakes ### TypeScript/JavaScript Common Mistakes
- [ ] `==` instead of `===` - [ ] `==` instead of `===`
- [ ] Modifying array/object during iteration - [ ] Modifying array/object during iteration
- [ ] `this` context lost in callbacks - [ ] `this` context lost in callbacks
@@ -648,21 +675,23 @@ if (isFetching) return <Refreshing />; // 正在后台刷新
## Vue 3 ## Vue 3
### 响应性丢失 ### 响应性丢失
```vue ```vue
<!-- 解构 reactive 丢失响应性 --> <!-- 解构 reactive 丢失响应性 -->
<script setup> <script setup>
const state = reactive({ count: 0 }) const state = reactive({ count: 0 });
const { count } = state // count 不是响应式的! const { count } = state; // count 不是响应式的!
</script> </script>
<!-- 使用 toRefs --> <!-- 使用 toRefs -->
<script setup> <script setup>
const state = reactive({ count: 0 }) const state = reactive({ count: 0 });
const { count } = toRefs(state) // count.value 是响应式的 const { count } = toRefs(state); // count.value 是响应式的
</script> </script>
``` ```
### Props 响应性传递 ### Props 响应性传递
```vue ```vue
<!-- 传递 props 值到 composable 丢失响应性 --> <!-- 传递 props 值到 composable 丢失响应性 -->
<script setup> <script setup>
@@ -680,48 +709,53 @@ const { data } = useFetch(toRef(props, 'id'))
``` ```
### Watch 清理 ### Watch 清理
```vue ```vue
<!-- 异步 watch 无清理导致竞态 --> <!-- 异步 watch 无清理导致竞态 -->
<script setup> <script setup>
watch(id, async (newId) => { watch(id, async (newId) => {
const data = await fetchData(newId) const data = await fetchData(newId);
result.value = data // 旧请求可能覆盖新结果! result.value = data; // 旧请求可能覆盖新结果!
}) });
</script> </script>
<!-- 使用 onCleanup 取消旧请求 --> <!-- 使用 onCleanup 取消旧请求 -->
<script setup> <script setup>
watch(id, async (newId, _, onCleanup) => { watch(id, async (newId, _, onCleanup) => {
const controller = new AbortController() const controller = new AbortController();
onCleanup(() => controller.abort()) onCleanup(() => controller.abort());
const data = await fetchData(newId, controller.signal) const data = await fetchData(newId, controller.signal);
result.value = data result.value = data;
}) });
</script> </script>
``` ```
### Computed 副作用 ### Computed 副作用
```vue ```vue
<!-- computed 中修改其他状态 --> <!-- computed 中修改其他状态 -->
<script setup> <script setup>
const total = computed(() => { const total = computed(() => {
sideEffect.value++ // 副作用!每次访问都会执行 sideEffect.value++; // 副作用!每次访问都会执行
return items.value.reduce((a, b) => a + b, 0) return items.value.reduce((a, b) => a + b, 0);
}) });
</script> </script>
<!-- computed 只做纯计算 --> <!-- computed 只做纯计算 -->
<script setup> <script setup>
const total = computed(() => { const total = computed(() => {
return items.value.reduce((a, b) => a + b, 0) return items.value.reduce((a, b) => a + b, 0);
}) });
// 副作用放 watch // 副作用放 watch
watch(total, () => { sideEffect.value++ }) watch(total, () => {
sideEffect.value++;
});
</script> </script>
``` ```
### 模板常见错误 ### 模板常见错误
```vue ```vue
<!-- v-if v-for 同时使用v-if 优先级更高 --> <!-- v-if v-for 同时使用v-if 优先级更高 -->
<template> <template>
@@ -739,6 +773,7 @@ watch(total, () => { sideEffect.value++ })
``` ```
### Common Mistakes ### Common Mistakes
- [ ] 解构 reactive 对象丢失响应性 - [ ] 解构 reactive 对象丢失响应性
- [ ] props 传递给 composable 时未保持响应性 - [ ] props 传递给 composable 时未保持响应性
- [ ] watch 异步回调无清理函数 - [ ] watch 异步回调无清理函数
@@ -753,6 +788,7 @@ watch(total, () => { sideEffect.value++ })
## Python ## Python
### Mutable Default Arguments ### Mutable Default Arguments
```python ```python
# ❌ Bug: List shared across all calls # ❌ Bug: List shared across all calls
def add_item(item, items=[]): def add_item(item, items=[]):
@@ -768,6 +804,7 @@ def add_item(item, items=None):
``` ```
### Exception Handling ### Exception Handling
```python ```python
# ❌ Catching everything, including KeyboardInterrupt # ❌ Catching everything, including KeyboardInterrupt
try: try:
@@ -784,6 +821,7 @@ except ValueError as e:
``` ```
### Class Attributes ### Class Attributes
```python ```python
# ❌ Shared mutable class attribute # ❌ Shared mutable class attribute
class User: class User:
@@ -796,6 +834,7 @@ class User:
``` ```
### Common Mistakes ### Common Mistakes
- [ ] Using `is` instead of `==` for value comparison - [ ] Using `is` instead of `==` for value comparison
- [ ] Forgetting `self` parameter in methods - [ ] Forgetting `self` parameter in methods
- [ ] Modifying list while iterating - [ ] Modifying list while iterating
@@ -1139,18 +1178,21 @@ struct Good<'a> {
### Rust 审查清单 ### Rust 审查清单
**所有权与借用** **所有权与借用**
- [ ] clone() 是有意为之,不是绕过借用检查器 - [ ] clone() 是有意为之,不是绕过借用检查器
- [ ] 避免在结构体中存储借用(除非必要) - [ ] 避免在结构体中存储借用(除非必要)
- [ ] Rc/Arc 使用合理,没有隐藏不必要的共享状态 - [ ] Rc/Arc 使用合理,没有隐藏不必要的共享状态
- [ ] 没有不必要的 RefCell(运行时检查 vs 编译时) - [ ] 没有不必要的 RefCell(运行时检查 vs 编译时)
**Unsafe 代码** **Unsafe 代码**
- [ ] 每个 unsafe 块有 SAFETY 注释 - [ ] 每个 unsafe 块有 SAFETY 注释
- [ ] unsafe fn 有 # Safety 文档 - [ ] unsafe fn 有 # Safety 文档
- [ ] 安全不变量被清晰记录 - [ ] 安全不变量被清晰记录
- [ ] unsafe 边界尽可能小 - [ ] unsafe 边界尽可能小
**异步/并发** **异步/并发**
- [ ] 没有在异步上下文中阻塞 - [ ] 没有在异步上下文中阻塞
- [ ] 没有跨 .await 持有 std::sync 锁 - [ ] 没有跨 .await 持有 std::sync 锁
- [ ] spawn 的任务满足 'static 约束 - [ ] spawn 的任务满足 'static 约束
@@ -1158,25 +1200,29 @@ struct Good<'a> {
- [ ] 锁的顺序一致(避免死锁) - [ ] 锁的顺序一致(避免死锁)
**错误处理** **错误处理**
- [ ] 库代码使用 thiserror,应用代码使用 anyhow - [ ] 库代码使用 thiserror,应用代码使用 anyhow
- [ ] 错误有足够的上下文信息 - [ ] 错误有足够的上下文信息
- [ ] 没有在生产代码中 unwrap/expect - [ ] 没有在生产代码中 unwrap/expect
- [ ] must_use 返回值被正确处理 - [ ] must_use 返回值被正确处理
**性能** **性能**
- [ ] 避免不必要的 collect() - [ ] 避免不必要的 collect()
- [ ] 大数据结构传引用 - [ ] 大数据结构传引用
- [ ] 字符串拼接使用 String::with_capacity 或 write! - [ ] 字符串拼接使用 String::with_capacity 或 write!
- [ ] impl Trait 优于 Box<dyn Trait>(当可能时) - [ ] impl Trait 优于 Box<dyn Trait>(当可能时)
**类型系统** **类型系统**
- [ ] 善用 newtype 模式增加类型安全 - [ ] 善用 newtype 模式增加类型安全
- [ ] 枚举穷尽匹配(没有 _ 通配符隐藏新变体) - [ ] 枚举穷尽匹配(没有 \_ 通配符隐藏新变体)
- [ ] 生命周期尽可能简化 - [ ] 生命周期尽可能简化
## SQL ## SQL
### Injection Vulnerabilities ### Injection Vulnerabilities
```sql ```sql
-- ❌ String concatenation (SQL injection risk) -- ❌ String concatenation (SQL injection risk)
query = "SELECT * FROM users WHERE id = " + user_id query = "SELECT * FROM users WHERE id = " + user_id
@@ -1187,13 +1233,15 @@ cursor.execute(query, (user_id,))
``` ```
### Performance Issues ### Performance Issues
- [ ] Missing indexes on filtered/joined columns - [ ] Missing indexes on filtered/joined columns
- [ ] SELECT * instead of specific columns - [ ] SELECT \* instead of specific columns
- [ ] N+1 query patterns - [ ] N+1 query patterns
- [ ] Missing LIMIT on large tables - [ ] Missing LIMIT on large tables
- [ ] Inefficient subqueries vs JOINs - [ ] Inefficient subqueries vs JOINs
### Common Mistakes ### Common Mistakes
- [ ] Not handling NULL comparisons correctly - [ ] Not handling NULL comparisons correctly
- [ ] Missing transactions for related operations - [ ] Missing transactions for related operations
- [ ] Incorrect JOIN types - [ ] Incorrect JOIN types
@@ -1203,6 +1251,7 @@ cursor.execute(query, (user_id,))
## API Design ## API Design
### REST Issues ### REST Issues
- [ ] Inconsistent resource naming - [ ] Inconsistent resource naming
- [ ] Wrong HTTP methods (POST for idempotent operations) - [ ] Wrong HTTP methods (POST for idempotent operations)
- [ ] Missing pagination for list endpoints - [ ] Missing pagination for list endpoints
@@ -1210,6 +1259,7 @@ cursor.execute(query, (user_id,))
- [ ] Missing rate limiting - [ ] Missing rate limiting
### Data Validation ### Data Validation
- [ ] Missing input validation - [ ] Missing input validation
- [ ] Incorrect data type validation - [ ] Incorrect data type validation
- [ ] Missing length/range checks - [ ] Missing length/range checks
@@ -1219,6 +1269,7 @@ cursor.execute(query, (user_id,))
## Testing ## Testing
### Test Quality Issues ### Test Quality Issues
- [ ] Testing implementation details instead of behavior - [ ] Testing implementation details instead of behavior
- [ ] Missing edge case tests - [ ] Missing edge case tests
- [ ] Flaky tests (non-deterministic) - [ ] Flaky tests (non-deterministic)
@@ -357,6 +357,7 @@ clang-format -i src/*.cpp include/*.h
## Review Checklist ## Review Checklist
### Safety and Lifetime ### Safety and Lifetime
- [ ] Ownership is explicit (RAII, unique_ptr by default) - [ ] Ownership is explicit (RAII, unique_ptr by default)
- [ ] No dangling references or views - [ ] No dangling references or views
- [ ] Rule of 0/3/5 followed for resource-owning types - [ ] Rule of 0/3/5 followed for resource-owning types
@@ -364,22 +365,26 @@ clang-format -i src/*.cpp include/*.h
- [ ] Destructors are noexcept and do not throw - [ ] Destructors are noexcept and do not throw
### API and Design ### API and Design
- [ ] const-correctness is applied consistently - [ ] const-correctness is applied consistently
- [ ] Constructors are explicit where needed - [ ] Constructors are explicit where needed
- [ ] Override/final used for virtual functions - [ ] Override/final used for virtual functions
- [ ] No object slicing (pass by ref or pointer) - [ ] No object slicing (pass by ref or pointer)
### Concurrency ### Concurrency
- [ ] Shared data is protected (mutex or atomics) - [ ] Shared data is protected (mutex or atomics)
- [ ] Locking order is consistent - [ ] Locking order is consistent
- [ ] No blocking while holding locks - [ ] No blocking while holding locks
### Performance ### Performance
- [ ] Unnecessary allocations avoided (reserve, move) - [ ] Unnecessary allocations avoided (reserve, move)
- [ ] Copies avoided in hot paths - [ ] Copies avoided in hot paths
- [ ] Algorithmic complexity is reasonable - [ ] Algorithmic complexity is reasonable
### Tooling and Tests ### Tooling and Tests
- [ ] Builds clean with warnings enabled - [ ] Builds clean with warnings enabled
- [ ] Sanitizers run on critical code paths - [ ] Sanitizers run on critical code paths
- [ ] Static analysis (clang-tidy) results are addressed - [ ] Static analysis (clang-tidy) results are addressed
@@ -105,8 +105,12 @@ CSS 及预处理器代码审查指南,覆盖性能、可维护性、响应式
```css ```css
/* ✅ 工具类 - 明确需要覆盖 */ /* ✅ 工具类 - 明确需要覆盖 */
.hidden { display: none !important; } .hidden {
.sr-only { position: absolute !important; } display: none !important;
}
.sr-only {
position: absolute !important;
}
/* ✅ 覆盖第三方库样式(无法修改源码时) */ /* ✅ 覆盖第三方库样式(无法修改源码时) */
.third-party-modal { .third-party-modal {
@@ -115,7 +119,9 @@ CSS 及预处理器代码审查指南,覆盖性能、可维护性、响应式
/* ✅ 打印样式 */ /* ✅ 打印样式 */
@media print { @media print {
.no-print { display: none !important; } .no-print {
display: none !important;
}
} }
``` ```
@@ -124,16 +130,20 @@ CSS 及预处理器代码审查指南,覆盖性能、可维护性、响应式
```css ```css
/* ❌ 解决特异性问题 - 应该重构选择器 */ /* ❌ 解决特异性问题 - 应该重构选择器 */
.button { .button {
background: blue !important; /* 为什么需要 !important? */ background: blue !important; /* 为什么需要 !important? */
} }
/* ❌ 覆盖自己写的样式 */ /* ❌ 覆盖自己写的样式 */
.card { padding: 20px; } .card {
.card { padding: 30px !important; } /* 直接修改原规则 */ padding: 20px;
}
.card {
padding: 30px !important;
} /* 直接修改原规则 */
/* ❌ 在组件样式中 */ /* ❌ 在组件样式中 */
.my-component .title { .my-component .title {
font-size: 24px !important; /* 破坏组件封装 */ font-size: 24px !important; /* 破坏组件封装 */
} }
``` ```
@@ -159,10 +169,10 @@ button.my-btn {
/* ✅ 使用 :where() 降低被覆盖样式的特异性 */ /* ✅ 使用 :where() 降低被覆盖样式的特异性 */
:where(.btn) { :where(.btn) {
background: blue; /* 特异性为 0 */ background: blue; /* 特异性为 0 */
} }
.my-btn { .my-btn {
background: red; /* 可以正常覆盖 */ background: red; /* 可以正常覆盖 */
} }
``` ```
@@ -190,7 +200,9 @@ button.my-btn {
/* ✅ 明确指定属性 */ /* ✅ 明确指定属性 */
.button { .button {
transition: background-color 0.3s ease, transform 0.3s ease; transition:
background-color 0.3s ease,
transform 0.3s ease;
} }
/* ✅ 多属性时使用变量 */ /* ✅ 多属性时使用变量 */
@@ -208,11 +220,11 @@ button.my-btn {
```css ```css
/* ❌ 每帧触发重绘 - 严重影响性能 */ /* ❌ 每帧触发重绘 - 严重影响性能 */
.card { .card {
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.3s ease; transition: box-shadow 0.3s ease;
} }
.card:hover { .card:hover {
box-shadow: 0 8px 16px rgba(0,0,0,0.2); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
} }
/* ✅ 使用伪元素 + opacity */ /* ✅ 使用伪元素 + opacity */
@@ -223,7 +235,7 @@ button.my-btn {
content: ''; content: '';
position: absolute; position: absolute;
inset: 0; inset: 0;
box-shadow: 0 8px 16px rgba(0,0,0,0.2); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
opacity: 0; opacity: 0;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
pointer-events: none; pointer-events: none;
@@ -239,24 +251,31 @@ button.my-btn {
```css ```css
/* ❌ 动画这些属性会触发布局重计算 */ /* ❌ 动画这些属性会触发布局重计算 */
.bad-animation { .bad-animation {
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s, margin 0.3s; transition:
width 0.3s,
height 0.3s,
top 0.3s,
left 0.3s,
margin 0.3s;
} }
/* ✅ 只动画 transform 和 opacity(仅触发合成) */ /* ✅ 只动画 transform 和 opacity(仅触发合成) */
.good-animation { .good-animation {
transition: transform 0.3s, opacity 0.3s; transition:
transform 0.3s,
opacity 0.3s;
} }
/* 位移用 translate 代替 top/left */ /* 位移用 translate 代替 top/left */
.move { .move {
transform: translateX(100px); /* ✅ */ transform: translateX(100px); /* ✅ */
/* left: 100px; */ /* ❌ */ /* left: 100px; */ /* ❌ */
} }
/* 缩放用 scale 代替 width/height */ /* 缩放用 scale 代替 width/height */
.grow { .grow {
transform: scale(1.1); /* ✅ */ transform: scale(1.1); /* ✅ */
/* width: 110%; */ /* ❌ */ /* width: 110%; */ /* ❌ */
} }
``` ```
@@ -276,11 +295,17 @@ button.my-btn {
} }
/* ❌ 通配符选择器 */ /* ❌ 通配符选择器 */
* { box-sizing: border-box; } /* 影响所有元素 */ * {
[class*="icon-"] { display: inline; } /* 属性选择器较慢 */ box-sizing: border-box;
} /* 影响所有元素 */
[class*='icon-'] {
display: inline;
} /* 属性选择器较慢 */
/* ✅ 限制范围 */ /* ✅ 限制范围 */
.icon-box * { box-sizing: border-box; } .icon-box * {
box-sizing: border-box;
}
``` ```
#### 大量阴影和滤镜 #### 大量阴影和滤镜
@@ -289,17 +314,17 @@ button.my-btn {
/* ⚠️ 复杂阴影影响渲染性能 */ /* ⚠️ 复杂阴影影响渲染性能 */
.heavy-shadow { .heavy-shadow {
box-shadow: box-shadow:
0 1px 2px rgba(0,0,0,0.1), 0 1px 2px rgba(0, 0, 0, 0.1),
0 2px 4px rgba(0,0,0,0.1), 0 2px 4px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(0,0,0,0.1), 0 4px 8px rgba(0, 0, 0, 0.1),
0 8px 16px rgba(0,0,0,0.1), 0 8px 16px rgba(0, 0, 0, 0.1),
0 16px 32px rgba(0,0,0,0.1); /* 5 层阴影 */ 0 16px 32px rgba(0, 0, 0, 0.1); /* 5 层阴影 */
} }
/* ⚠️ 滤镜消耗 GPU */ /* ⚠️ 滤镜消耗 GPU */
.blur-heavy { .blur-heavy {
filter: blur(20px) brightness(1.2) contrast(1.1); filter: blur(20px) brightness(1.2) contrast(1.1);
backdrop-filter: blur(10px); /* 更消耗性能 */ backdrop-filter: blur(10px); /* 更消耗性能 */
} }
``` ```
@@ -318,7 +343,7 @@ button.my-btn {
/* 使用 contain 限制重绘范围 */ /* 使用 contain 限制重绘范围 */
.card { .card {
contain: layout paint; /* 告诉浏览器内部变化不影响外部 */ contain: layout paint; /* 告诉浏览器内部变化不影响外部 */
} }
``` ```
@@ -387,16 +412,20 @@ button.my-btn {
```css ```css
/* 推荐断点(基于内容而非设备) */ /* 推荐断点(基于内容而非设备) */
:root { :root {
--breakpoint-sm: 640px; /* 大手机 */ --breakpoint-sm: 640px; /* 大手机 */
--breakpoint-md: 768px; /* 平板竖屏 */ --breakpoint-md: 768px; /* 平板竖屏 */
--breakpoint-lg: 1024px; /* 平板横屏/小笔记本 */ --breakpoint-lg: 1024px; /* 平板横屏/小笔记本 */
--breakpoint-xl: 1280px; /* 桌面 */ --breakpoint-xl: 1280px; /* 桌面 */
--breakpoint-2xl: 1536px; /* 大桌面 */ --breakpoint-2xl: 1536px; /* 大桌面 */
} }
/* 使用示例 */ /* 使用示例 */
@media (min-width: 768px) { /* md */ } @media (min-width: 768px) {
@media (min-width: 1024px) { /* lg */ } /* md */
}
@media (min-width: 1024px) {
/* lg */
}
``` ```
### 响应式审查清单 ### 响应式审查清单
@@ -425,7 +454,7 @@ button.my-btn {
/* ❌ 固定高度的文本容器 */ /* ❌ 固定高度的文本容器 */
.text-box { .text-box {
height: 100px; /* 文字可能溢出 */ height: 100px; /* 文字可能溢出 */
} }
/* ✅ 最小高度 */ /* ✅ 最小高度 */
@@ -435,7 +464,7 @@ button.my-btn {
/* ❌ 小触摸目标 */ /* ❌ 小触摸目标 */
.small-button { .small-button {
padding: 4px 8px; /* 太小,难以点击 */ padding: 4px 8px; /* 太小,难以点击 */
} }
/* ✅ 足够的触摸区域 */ /* ✅ 足够的触摸区域 */
@@ -452,22 +481,22 @@ button.my-btn {
### 需要检查的特性 ### 需要检查的特性
| 特性 | 兼容性 | 建议 | | 特性 | 兼容性 | 建议 |
|------|--------|------| | ------------------- | ------------- | --------------------------- |
| CSS Grid | 现代浏览器 ✅ | IE 需要 Autoprefixer + 测试 | | CSS Grid | 现代浏览器 ✅ | IE 需要 Autoprefixer + 测试 |
| Flexbox | 广泛支持 ✅ | 旧版需要前缀 | | Flexbox | 广泛支持 ✅ | 旧版需要前缀 |
| CSS Variables | 现代浏览器 ✅ | IE 不支持,需要回退 | | CSS Variables | 现代浏览器 ✅ | IE 不支持,需要回退 |
| `gap` (flexbox) | 较新 ⚠️ | Safari 14.1+ | | `gap` (flexbox) | 较新 ⚠️ | Safari 14.1+ |
| `:has()` | 较新 ⚠️ | Firefox 121+ | | `:has()` | 较新 ⚠️ | Firefox 121+ |
| `container queries` | 较新 ⚠️ | 2023 年后的浏览器 | | `container queries` | 较新 ⚠️ | 2023 年后的浏览器 |
| `@layer` | 较新 ⚠️ | 检查目标浏览器 | | `@layer` | 较新 ⚠️ | 检查目标浏览器 |
### 回退策略 ### 回退策略
```css ```css
/* CSS 变量回退 */ /* CSS 变量回退 */
.button { .button {
background: #3b82f6; /* 回退值 */ background: #3b82f6; /* 回退值 */
background: var(--color-primary); /* 现代浏览器 */ background: var(--color-primary); /* 现代浏览器 */
} }
@@ -540,7 +569,7 @@ module.exports = {
.content { .content {
.article { .article {
.title { .title {
color: red; // 编译为 .page .container .content .article .title color: red; // 编译为 .page .container .content .article .title
} }
} }
} }
@@ -554,7 +583,9 @@ module.exports = {
} }
&__content { &__content {
p { margin-bottom: 1em; } p {
margin-bottom: 1em;
}
} }
} }
``` ```
@@ -637,13 +668,13 @@ $primary-color: #3b82f6;
## 工具推荐 ## 工具推荐
| 工具 | 用途 | | 工具 | 用途 |
|------|------| | ----------------------------------------------- | ---------------- |
| [Stylelint](https://stylelint.io/) | CSS 代码检查 | | [Stylelint](https://stylelint.io/) | CSS 代码检查 |
| [PurgeCSS](https://purgecss.com/) | 移除未使用 CSS | | [PurgeCSS](https://purgecss.com/) | 移除未使用 CSS |
| [Autoprefixer](https://autoprefixer.github.io/) | 自动添加前缀 | | [Autoprefixer](https://autoprefixer.github.io/) | 自动添加前缀 |
| [CSS Stats](https://cssstats.com/) | 分析 CSS 统计 | | [CSS Stats](https://cssstats.com/) | 分析 CSS 统计 |
| [Can I Use](https://caniuse.com/) | 浏览器兼容性查询 | | [Can I Use](https://caniuse.com/) | 浏览器兼容性查询 |
--- ---
@@ -5,6 +5,7 @@
## 快速审查清单 ## 快速审查清单
### 必查项 ### 必查项
- [ ] 错误是否正确处理(不忽略、有上下文) - [ ] 错误是否正确处理(不忽略、有上下文)
- [ ] goroutine 是否有退出机制(避免泄漏) - [ ] goroutine 是否有退出机制(避免泄漏)
- [ ] context 是否正确传递和取消 - [ ] context 是否正确传递和取消
@@ -12,6 +13,7 @@
- [ ] 是否使用 `gofmt` 格式化代码 - [ ] 是否使用 `gofmt` 格式化代码
### 高频问题 ### 高频问题
- [ ] 循环变量捕获问题(Go < 1.22 - [ ] 循环变量捕获问题(Go < 1.22
- [ ] nil 检查是否完整 - [ ] nil 检查是否完整
- [ ] map 是否初始化后使用 - [ ] map 是否初始化后使用
@@ -377,29 +377,34 @@ class UserRepositoryTest {
## Review Checklist ## Review Checklist
### 基础与规范 ### 基础与规范
- [ ] 遵循 Java 17/21 新特性(Switch 表达式, Records, 文本块) - [ ] 遵循 Java 17/21 新特性(Switch 表达式, Records, 文本块)
- [ ] 避免使用已过时的类(Date, Calendar, SimpleDateFormat - [ ] 避免使用已过时的类(Date, Calendar, SimpleDateFormat
- [ ] 集合操作是否优先使用了 Stream API 或 Collections 方法? - [ ] 集合操作是否优先使用了 Stream API 或 Collections 方法?
- [ ] Optional 仅用于返回值,未用于字段或参数 - [ ] Optional 仅用于返回值,未用于字段或参数
### Spring Boot ### Spring Boot
- [ ] 使用构造器注入而非 @Autowired 字段注入 - [ ] 使用构造器注入而非 @Autowired 字段注入
- [ ] 配置属性使用了 @ConfigurationProperties - [ ] 配置属性使用了 @ConfigurationProperties
- [ ] Controller 职责单一,业务逻辑下沉到 Service - [ ] Controller 职责单一,业务逻辑下沉到 Service
- [ ] 全局异常处理使用了 @ControllerAdvice / ProblemDetail - [ ] 全局异常处理使用了 @ControllerAdvice / ProblemDetail
### 数据库 & 事务 ### 数据库 & 事务
- [ ] 读操作事务标记了 `@Transactional(readOnly = true)` - [ ] 读操作事务标记了 `@Transactional(readOnly = true)`
- [ ] 检查是否存在 N+1 查询(EAGER fetch 或循环调用) - [ ] 检查是否存在 N+1 查询(EAGER fetch 或循环调用)
- [ ] Entity 类未使用 @Data,正确实现了 equals/hashCode - [ ] Entity 类未使用 @Data,正确实现了 equals/hashCode
- [ ] 数据库索引是否覆盖了查询条件 - [ ] 数据库索引是否覆盖了查询条件
### 并发与性能 ### 并发与性能
- [ ] I/O 密集型任务是否考虑了虚拟线程? - [ ] I/O 密集型任务是否考虑了虚拟线程?
- [ ] 线程安全类是否使用正确(ConcurrentHashMap vs HashMap - [ ] 线程安全类是否使用正确(ConcurrentHashMap vs HashMap
- [ ] 锁的粒度是否合理?避免在锁内进行 I/O 操作 - [ ] 锁的粒度是否合理?避免在锁内进行 I/O 操作
### 可维护性 ### 可维护性
- [ ] 关键业务逻辑有充分的单元测试 - [ ] 关键业务逻辑有充分的单元测试
- [ ] 日志记录恰当(使用 Slf4j,避免 System.out - [ ] 日志记录恰当(使用 Slf4j,避免 System.out
- [ ] 魔法值提取为常量或枚举 - [ ] 魔法值提取为常量或枚举
@@ -18,13 +18,13 @@
### 2024 核心指标 ### 2024 核心指标
| 指标 | 全称 | 目标值 | 含义 | | 指标 | 全称 | 目标值 | 含义 |
|------|------|--------|------| | ------- | ------------------------- | ------- | ------------------------------- |
| **LCP** | Largest Contentful Paint | ≤ 2.5s | 最大内容绘制时间 | | **LCP** | Largest Contentful Paint | ≤ 2.5s | 最大内容绘制时间 |
| **INP** | Interaction to Next Paint | ≤ 200ms | 交互响应时间(2024 年替代 FID)| | **INP** | Interaction to Next Paint | ≤ 200ms | 交互响应时间(2024 年替代 FID) |
| **CLS** | Cumulative Layout Shift | ≤ 0.1 | 累积布局偏移 | | **CLS** | Cumulative Layout Shift | ≤ 0.1 | 累积布局偏移 |
| **FCP** | First Contentful Paint | ≤ 1.8s | 首次内容绘制 | | **FCP** | First Contentful Paint | ≤ 1.8s | 首次内容绘制 |
| **TBT** | Total Blocking Time | ≤ 200ms | 主线程阻塞时间 | | **TBT** | Total Blocking Time | ≤ 200ms | 主线程阻塞时间 |
### LCP 优化检查 ### LCP 优化检查
@@ -47,6 +47,7 @@
``` ```
**审查要点:** **审查要点:**
- [ ] LCP 元素是否设置 `fetchpriority="high"` - [ ] LCP 元素是否设置 `fetchpriority="high"`
- [ ] 是否使用 WebP/AVIF 格式? - [ ] 是否使用 WebP/AVIF 格式?
- [ ] 是否有服务端渲染或静态生成? - [ ] 是否有服务端渲染或静态生成?
@@ -59,21 +60,17 @@
<link rel="stylesheet" href="all-styles.css" /> <link rel="stylesheet" href="all-styles.css" />
<!-- ✅ 关键 CSS 内联 + 异步加载其余 --> <!-- ✅ 关键 CSS 内联 + 异步加载其余 -->
<style>/* 首屏关键样式 */</style> <style>
/* 首屏关键样式 */
</style>
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'" /> <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
<!-- ❌ 阻塞渲染的字体 --> <!-- ❌ 阻塞渲染的字体 -->
@font-face { @font-face { font-family: 'CustomFont'; src: url('font.woff2'); }
font-family: 'CustomFont';
src: url('font.woff2');
}
<!-- ✅ 字体显示优化 --> <!-- ✅ 字体显示优化 -->
@font-face { @font-face { font-family: 'CustomFont'; src: url('font.woff2'); font-display: swap; /*
font-family: 'CustomFont'; 先用系统字体,加载后切换 */ }
src: url('font.woff2');
font-display: swap; /* 先用系统字体,加载后切换 */
}
``` ```
### INP 优化检查 ### INP 优化检查
@@ -89,7 +86,7 @@ button.addEventListener('click', () => {
// ✅ 拆分长任务 // ✅ 拆分长任务
button.addEventListener('click', async () => { button.addEventListener('click', async () => {
// 让出主线程 // 让出主线程
await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0)); (await scheduler.yield?.()) ?? new Promise((r) => setTimeout(r, 0));
// 分批处理 // 分批处理
for (const chunk of chunks) { for (const chunk of chunks) {
@@ -109,7 +106,9 @@ worker.onmessage = (e) => updateUI(e.data);
```css ```css
/* ❌ 未指定尺寸的媒体 */ /* ❌ 未指定尺寸的媒体 */
img { width: 100%; } img {
width: 100%;
}
/* ✅ 预留空间 */ /* ✅ 预留空间 */
img { img {
@@ -118,7 +117,8 @@ img {
} }
/* ❌ 动态插入内容导致布局偏移 */ /* ❌ 动态插入内容导致布局偏移 */
.ad-container { } .ad-container {
}
/* ✅ 预留固定高度 */ /* ✅ 预留固定高度 */
.ad-container { .ad-container {
@@ -127,6 +127,7 @@ img {
``` ```
**CLS 审查清单:** **CLS 审查清单:**
- [ ] 图片/视频是否有 width/height 或 aspect-ratio - [ ] 图片/视频是否有 width/height 或 aspect-ratio
- [ ] 字体加载是否使用 `font-display: swap` - [ ] 字体加载是否使用 `font-display: swap`
- [ ] 动态内容是否预留空间? - [ ] 动态内容是否预留空间?
@@ -175,7 +176,7 @@ import { format } from 'date-fns';
// ❌ 未使用 Tree Shaking // ❌ 未使用 Tree Shaking
export default { export default {
fn1() {}, fn1() {},
fn2() {}, // 未使用但被打包 fn2() {}, // 未使用但被打包
}; };
// ✅ 命名导出支持 Tree Shaking // ✅ 命名导出支持 Tree Shaking
@@ -184,6 +185,7 @@ export function fn2() {}
``` ```
**Bundle 审查清单:** **Bundle 审查清单:**
- [ ] 是否使用动态 import() 进行代码分割? - [ ] 是否使用动态 import() 进行代码分割?
- [ ] 大型库是否按需导入? - [ ] 大型库是否按需导入?
- [ ] 是否分析过 bundle 大小?(webpack-bundle-analyzer - [ ] 是否分析过 bundle 大小?(webpack-bundle-analyzer
@@ -196,9 +198,11 @@ export function fn2() {}
function List({ items }) { function List({ items }) {
return ( return (
<ul> <ul>
{items.map(item => <li key={item.id}>{item.name}</li>)} {items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul> </ul>
); // 10000 条数据 = 10000 个 DOM 节点 ); // 10000 条数据 = 10000 个 DOM 节点
} }
// ✅ 虚拟列表 - 只渲染可见项 // ✅ 虚拟列表 - 只渲染可见项
@@ -206,20 +210,15 @@ import { FixedSizeList } from 'react-window';
function VirtualList({ items }) { function VirtualList({ items }) {
return ( return (
<FixedSizeList <FixedSizeList height={400} itemCount={items.length} itemSize={35}>
height={400} {({ index, style }) => <div style={style}>{items[index].name}</div>}
itemCount={items.length}
itemSize={35}
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList> </FixedSizeList>
); );
} }
``` ```
**大数据审查要点:** **大数据审查要点:**
- [ ] 列表超过 100 项是否使用虚拟滚动? - [ ] 列表超过 100 项是否使用虚拟滚动?
- [ ] 表格是否支持分页或虚拟化? - [ ] 表格是否支持分页或虚拟化?
- [ ] 是否有不必要的全量渲染? - [ ] 是否有不必要的全量渲染?
@@ -276,7 +275,7 @@ function createHandler() {
// ✅ 只保留必要数据 // ✅ 只保留必要数据
function createHandler() { function createHandler() {
const largeData = new Array(1000000).fill('x'); const largeData = new Array(1000000).fill('x');
const length = largeData.length; // 只保留需要的值 const length = largeData.length; // 只保留需要的值
return function handler() { return function handler() {
console.log(length); console.log(length);
@@ -314,11 +313,11 @@ useEffect(() => {
### 检测工具 ### 检测工具
| 工具 | 用途 | | 工具 | 用途 |
|------|------| | ---------------------- | ------------------ |
| Chrome DevTools Memory | 堆快照分析 | | Chrome DevTools Memory | 堆快照分析 |
| MemLab (Meta) | 自动化内存泄漏检测 | | MemLab (Meta) | 自动化内存泄漏检测 |
| Performance Monitor | 实时内存监控 | | Performance Monitor | 实时内存监控 |
--- ---
@@ -346,7 +345,7 @@ posts = Post.objects.prefetch_related('tags').all()
// ❌ N+1 问题 // ❌ N+1 问题
const users = await userRepository.find(); const users = await userRepository.find();
for (const user of users) { for (const user of users) {
const posts = await user.posts; // 每次循环都查询 const posts = await user.posts; // 每次循环都查询
} }
// ✅ Eager Loading // ✅ Eager Loading
@@ -405,12 +404,14 @@ cursor.execute("SELECT * FROM users WHERE id IN %s", (tuple(user_ids),))
```markdown ```markdown
🔴 必须检查: 🔴 必须检查:
- [ ] 是否存在 N+1 查询? - [ ] 是否存在 N+1 查询?
- [ ] WHERE 子句列是否有索引? - [ ] WHERE 子句列是否有索引?
- [ ] 是否避免了 SELECT * - [ ] 是否避免了 SELECT \*
- [ ] 大表查询是否有 LIMIT? - [ ] 大表查询是否有 LIMIT?
🟡 建议检查: 🟡 建议检查:
- [ ] 是否使用了 EXPLAIN 分析查询计划? - [ ] 是否使用了 EXPLAIN 分析查询计划?
- [ ] 复合索引列顺序是否正确? - [ ] 复合索引列顺序是否正确?
- [ ] 是否有未使用的索引? - [ ] 是否有未使用的索引?
@@ -426,14 +427,14 @@ cursor.execute("SELECT * FROM users WHERE id IN %s", (tuple(user_ids),))
```javascript ```javascript
// ❌ 返回全部数据 // ❌ 返回全部数据
app.get('/users', async (req, res) => { app.get('/users', async (req, res) => {
const users = await User.findAll(); // 可能返回 100000 条 const users = await User.findAll(); // 可能返回 100000 条
res.json(users); res.json(users);
}); });
// ✅ 分页 + 限制最大数量 // ✅ 分页 + 限制最大数量
app.get('/users', async (req, res) => { app.get('/users', async (req, res) => {
const page = parseInt(req.query.page) || 1; const page = parseInt(req.query.page) || 1;
const limit = Math.min(parseInt(req.query.limit) || 20, 100); // 最大 100 const limit = Math.min(parseInt(req.query.limit) || 20, 100); // 最大 100
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const { rows, count } = await User.findAndCountAll({ const { rows, count } = await User.findAndCountAll({
@@ -479,8 +480,8 @@ async function getUser(id) {
// ✅ HTTP 缓存头 // ✅ HTTP 缓存头
app.get('/static-data', (req, res) => { app.get('/static-data', (req, res) => {
res.set({ res.set({
'Cache-Control': 'public, max-age=86400', // 24 小时 'Cache-Control': 'public, max-age=86400', // 24 小时
'ETag': 'abc123', ETag: 'abc123',
}); });
res.json(data); res.json(data);
}); });
@@ -511,8 +512,8 @@ app.get('/users', async (req, res) => {
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ const limiter = rateLimit({
windowMs: 60 * 1000, // 1 分钟 windowMs: 60 * 1000, // 1 分钟
max: 100, // 最多 100 次请求 max: 100, // 最多 100 次请求
message: { error: 'Too many requests, please try again later.' }, message: { error: 'Too many requests, please try again later.' },
}); });
@@ -536,14 +537,14 @@ app.use('/api/', limiter);
### 常见复杂度对比 ### 常见复杂度对比
| 复杂度 | 名称 | 10 条 | 1000 条 | 100 万条 | 示例 | | 复杂度 | 名称 | 10 条 | 1000 条 | 100 万条 | 示例 |
|--------|------|-------|---------|----------|------| | ---------- | -------- | ----- | ------- | -------- | ------------ |
| O(1) | 常数 | 1 | 1 | 1 | 哈希查找 | | O(1) | 常数 | 1 | 1 | 1 | 哈希查找 |
| O(log n) | 对数 | 3 | 10 | 20 | 二分查找 | | O(log n) | 对数 | 3 | 10 | 20 | 二分查找 |
| O(n) | 线性 | 10 | 1000 | 100 万 | 遍历数组 | | O(n) | 线性 | 10 | 1000 | 100 万 | 遍历数组 |
| O(n log n) | 线性对数 | 33 | 10000 | 2000 万 | 快速排序 | | O(n log n) | 线性对数 | 33 | 10000 | 2000 万 | 快速排序 |
| O(n²) | 平方 | 100 | 100 万 | 1 万亿 | 嵌套循环 | | O(n²) | 平方 | 100 | 100 万 | 1 万亿 | 嵌套循环 |
| O(2ⁿ) | 指数 | 1024 | ∞ | ∞ | 递归斐波那契 | | O(2ⁿ) | 指数 | 1024 | ∞ | ∞ | 递归斐波那契 |
### 代码审查中的识别 ### 代码审查中的识别
@@ -580,7 +581,8 @@ function findDuplicates(arr) {
function removeDuplicates(arr) { function removeDuplicates(arr) {
const result = []; const result = [];
for (const item of arr) { for (const item of arr) {
if (!result.includes(item)) { // includes 是 O(n) if (!result.includes(item)) {
// includes 是 O(n)
result.push(item); result.push(item);
} }
} }
@@ -613,7 +615,7 @@ function getUser(id) {
```javascript ```javascript
// ⚠️ O(n) 空间 - 创建新数组 // ⚠️ O(n) 空间 - 创建新数组
const doubled = arr.map(x => x * 2); const doubled = arr.map((x) => x * 2);
// ✅ O(1) 空间 - 原地修改(如果允许) // ✅ O(1) 空间 - 原地修改(如果允许)
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
@@ -623,7 +625,7 @@ for (let i = 0; i < arr.length; i++) {
// ⚠️ 递归深度过大可能栈溢出 // ⚠️ 递归深度过大可能栈溢出
function factorial(n) { function factorial(n) {
if (n <= 1) return 1; if (n <= 1) return 1;
return n * factorial(n - 1); // O(n) 栈空间 return n * factorial(n - 1); // O(n) 栈空间
} }
// ✅ 迭代版本 O(1) 空间 // ✅ 迭代版本 O(1) 空间
@@ -651,34 +653,40 @@ function factorial(n) {
### 🔴 必须检查(阻塞级) ### 🔴 必须检查(阻塞级)
**前端:** **前端:**
- [ ] LCP 图片是否懒加载?(不应该) - [ ] LCP 图片是否懒加载?(不应该)
- [ ] 是否有 `transition: all` - [ ] 是否有 `transition: all`
- [ ] 是否动画 width/height/top/left - [ ] 是否动画 width/height/top/left
- [ ] 列表 >100 项是否虚拟化? - [ ] 列表 >100 项是否虚拟化?
**后端:** **后端:**
- [ ] 是否存在 N+1 查询? - [ ] 是否存在 N+1 查询?
- [ ] 列表接口是否有分页? - [ ] 列表接口是否有分页?
- [ ] 是否有 SELECT * 查大表? - [ ] 是否有 SELECT \* 查大表?
**通用:** **通用:**
- [ ] 是否有 O(n²) 或更差的嵌套循环? - [ ] 是否有 O(n²) 或更差的嵌套循环?
- [ ] useEffect/事件监听是否有清理? - [ ] useEffect/事件监听是否有清理?
### 🟡 建议检查(重要级) ### 🟡 建议检查(重要级)
**前端:** **前端:**
- [ ] 是否使用代码分割? - [ ] 是否使用代码分割?
- [ ] 大型库是否按需导入? - [ ] 大型库是否按需导入?
- [ ] 图片是否使用 WebP/AVIF - [ ] 图片是否使用 WebP/AVIF
- [ ] 是否有未使用的依赖? - [ ] 是否有未使用的依赖?
**后端:** **后端:**
- [ ] 热点数据是否有缓存? - [ ] 热点数据是否有缓存?
- [ ] WHERE 列是否有索引? - [ ] WHERE 列是否有索引?
- [ ] 是否有慢查询监控? - [ ] 是否有慢查询监控?
**API** **API**
- [ ] 是否启用响应压缩? - [ ] 是否启用响应压缩?
- [ ] 是否有速率限制? - [ ] 是否有速率限制?
- [ ] 是否只返回必要字段? - [ ] 是否只返回必要字段?
@@ -696,21 +704,21 @@ function factorial(n) {
### 前端指标 ### 前端指标
| 指标 | 好 | 需改进 | 差 | | 指标 | 好 | 需改进 | 差 |
|------|-----|--------|-----| | ---------------- | ------- | --------- | ------- |
| LCP | ≤ 2.5s | 2.5-4s | > 4s | | LCP | ≤ 2.5s | 2.5-4s | > 4s |
| INP | ≤ 200ms | 200-500ms | > 500ms | | INP | ≤ 200ms | 200-500ms | > 500ms |
| CLS | ≤ 0.1 | 0.1-0.25 | > 0.25 | | CLS | ≤ 0.1 | 0.1-0.25 | > 0.25 |
| FCP | ≤ 1.8s | 1.8-3s | > 3s | | FCP | ≤ 1.8s | 1.8-3s | > 3s |
| Bundle Size (JS) | < 200KB | 200-500KB | > 500KB | | Bundle Size (JS) | < 200KB | 200-500KB | > 500KB |
### 后端指标 ### 后端指标
| 指标 | 好 | 需改进 | 差 | | 指标 | 好 | 需改进 | 差 |
|------|-----|--------|-----| | ------------ | ------- | --------- | ------- |
| API 响应时间 | < 100ms | 100-500ms | > 500ms | | API 响应时间 | < 100ms | 100-500ms | > 500ms |
| 数据库查询 | < 50ms | 50-200ms | > 200ms | | 数据库查询 | < 50ms | 50-200ms | > 200ms |
| 页面加载 | < 3s | 3-5s | > 5s | | 页面加载 | < 3s | 3-5s | > 5s |
--- ---
@@ -718,27 +726,27 @@ function factorial(n) {
### 前端性能 ### 前端性能
| 工具 | 用途 | | 工具 | 用途 |
|------|------| | -------------------------------------------------------------------------------------- | -------------------- |
| [Lighthouse](https://developer.chrome.com/docs/lighthouse/) | Core Web Vitals 测试 | | [Lighthouse](https://developer.chrome.com/docs/lighthouse/) | Core Web Vitals 测试 |
| [WebPageTest](https://www.webpagetest.org/) | 详细性能分析 | | [WebPageTest](https://www.webpagetest.org/) | 详细性能分析 |
| [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) | Bundle 分析 | | [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) | Bundle 分析 |
| [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/) | 运行时性能分析 | | [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/) | 运行时性能分析 |
### 内存检测 ### 内存检测
| 工具 | 用途 | | 工具 | 用途 |
|------|------| | ----------------------------------------------------- | ------------------ |
| [MemLab](https://github.com/facebookincubator/memlab) | 自动化内存泄漏检测 | | [MemLab](https://github.com/facebookincubator/memlab) | 自动化内存泄漏检测 |
| Chrome Memory Tab | 堆快照分析 | | Chrome Memory Tab | 堆快照分析 |
### 后端性能 ### 后端性能
| 工具 | 用途 | | 工具 | 用途 |
|------|------| | -------------------------------------------------------------------------- | ------------------- |
| EXPLAIN | 数据库查询计划分析 | | EXPLAIN | 数据库查询计划分析 |
| [pganalyze](https://pganalyze.com/) | PostgreSQL 性能监控 | | [pganalyze](https://pganalyze.com/) | PostgreSQL 性能监控 |
| [New Relic](https://newrelic.com/) / [Datadog](https://www.datadoghq.com/) | APM 监控 | | [New Relic](https://newrelic.com/) / [Datadog](https://www.datadoghq.com/) | APM 监控 |
--- ---
@@ -1023,6 +1023,7 @@ def handle_response(response: dict):
## Review Checklist ## Review Checklist
### 类型安全 ### 类型安全
- [ ] 函数有类型注解(参数和返回值) - [ ] 函数有类型注解(参数和返回值)
- [ ] 使用 `Optional` 明确可能为 None - [ ] 使用 `Optional` 明确可能为 None
- [ ] 泛型类型正确使用 - [ ] 泛型类型正确使用
@@ -1030,6 +1031,7 @@ def handle_response(response: dict):
- [ ] 避免使用 `Any`,必要时添加注释说明 - [ ] 避免使用 `Any`,必要时添加注释说明
### 异步代码 ### 异步代码
- [ ] async/await 正确配对使用 - [ ] async/await 正确配对使用
- [ ] 没有在异步代码中使用阻塞调用 - [ ] 没有在异步代码中使用阻塞调用
- [ ] 正确处理 `CancelledError` - [ ] 正确处理 `CancelledError`
@@ -1037,18 +1039,21 @@ def handle_response(response: dict):
- [ ] 资源正确清理(async context manager - [ ] 资源正确清理(async context manager
### 异常处理 ### 异常处理
- [ ] 捕获特定异常类型,不使用裸 `except:` - [ ] 捕获特定异常类型,不使用裸 `except:`
- [ ] 异常链使用 `from` 保留原因 - [ ] 异常链使用 `from` 保留原因
- [ ] 自定义异常继承自合适的基类 - [ ] 自定义异常继承自合适的基类
- [ ] 异常信息有意义,便于调试 - [ ] 异常信息有意义,便于调试
### 数据结构 ### 数据结构
- [ ] 没有使用可变默认参数(list、dict、set - [ ] 没有使用可变默认参数(list、dict、set
- [ ] 类属性不是可变对象 - [ ] 类属性不是可变对象
- [ ] 选择正确的数据结构(set vs list 查找) - [ ] 选择正确的数据结构(set vs list 查找)
- [ ] 大数据集使用生成器而非列表 - [ ] 大数据集使用生成器而非列表
### 测试 ### 测试
- [ ] 测试覆盖率达标(建议 ≥80%) - [ ] 测试覆盖率达标(建议 ≥80%)
- [ ] 测试命名清晰描述测试场景 - [ ] 测试命名清晰描述测试场景
- [ ] 边界情况有测试覆盖 - [ ] 边界情况有测试覆盖
@@ -1056,6 +1061,7 @@ def handle_response(response: dict):
- [ ] 异步代码有对应的异步测试 - [ ] 异步代码有对应的异步测试
### 代码风格 ### 代码风格
- [ ] 遵循 PEP 8 风格指南 - [ ] 遵循 PEP 8 风格指南
- [ ] 函数和类有 docstring - [ ] 函数和类有 docstring
- [ ] 导入顺序正确(标准库、第三方、本地) - [ ] 导入顺序正确(标准库、第三方、本地)
@@ -1063,6 +1069,7 @@ def handle_response(response: dict):
- [ ] 使用现代 Python 特性(f-string、walrus operator 等) - [ ] 使用现代 Python 特性(f-string、walrus operator 等)
### 性能 ### 性能
- [ ] 避免循环中重复创建对象 - [ ] 避免循环中重复创建对象
- [ ] 字符串拼接使用 join - [ ] 字符串拼接使用 join
- [ ] 合理使用缓存(@lru_cache - [ ] 合理使用缓存(@lru_cache
@@ -17,6 +17,7 @@
## Object Model & Memory Management ## Object Model & Memory Management
### Use Parent-Child Ownership Mechanism ### Use Parent-Child Ownership Mechanism
Qt's `QObject` hierarchy automatically manages memory. For `QObject`, prefer setting a parent object over manual `delete` or smart pointers. Qt's `QObject` hierarchy automatically manages memory. For `QObject`, prefer setting a parent object over manual `delete` or smart pointers.
```cpp ```cpp
@@ -32,6 +33,7 @@ QLabel* l = new QLabel(w); // Owned by 'w'
``` ```
### Use Smart Pointers with QObject ### Use Smart Pointers with QObject
If a `QObject` has no parent, use `QScopedPointer` or `std::unique_ptr` with a custom deleter (use `deleteLater` if cross-thread). Avoid `std::shared_ptr` for `QObject` unless necessary, as it confuses the parent-child ownership system. If a `QObject` has no parent, use `QScopedPointer` or `std::unique_ptr` with a custom deleter (use `deleteLater` if cross-thread). Avoid `std::shared_ptr` for `QObject` unless necessary, as it confuses the parent-child ownership system.
```cpp ```cpp
@@ -46,6 +48,7 @@ if (safePtr) {
``` ```
### Use `deleteLater()` ### Use `deleteLater()`
For asynchronous deletion, especially in slots or event handlers, use `deleteLater()` instead of `delete` to ensure pending events in the event loop are processed. For asynchronous deletion, especially in slots or event handlers, use `deleteLater()` instead of `delete` to ensure pending events in the event loop are processed.
--- ---
@@ -53,6 +56,7 @@ For asynchronous deletion, especially in slots or event handlers, use `deleteLat
## Signals & Slots ## Signals & Slots
### Prefer Function Pointer Syntax ### Prefer Function Pointer Syntax
Use compile-time checked syntax (Qt 5+). Use compile-time checked syntax (Qt 5+).
```cpp ```cpp
@@ -64,12 +68,15 @@ connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue);
``` ```
### Connection Types ### Connection Types
Be explicit or aware of connection types when crossing threads. Be explicit or aware of connection types when crossing threads.
- `Qt::AutoConnection` (Default): Direct if same thread, Queued if different thread. - `Qt::AutoConnection` (Default): Direct if same thread, Queued if different thread.
- `Qt::QueuedConnection`: Always posts event (thread-safe across threads). - `Qt::QueuedConnection`: Always posts event (thread-safe across threads).
- `Qt::DirectConnection`: Immediate call (dangerous if accessing non-thread-safe data across threads). - `Qt::DirectConnection`: Immediate call (dangerous if accessing non-thread-safe data across threads).
### Avoid Loops ### Avoid Loops
Check logic that might cause infinite signal loops (e.g., `valueChanged` -> `setValue` -> `valueChanged`). Block signals or check for equality before setting values. Check logic that might cause infinite signal loops (e.g., `valueChanged` -> `setValue` -> `valueChanged`). Block signals or check for equality before setting values.
```cpp ```cpp
@@ -85,6 +92,7 @@ void MyClass::setValue(int v) {
## Containers & Strings ## Containers & Strings
### QString Efficiency ### QString Efficiency
- Use `QStringLiteral("...")` for compile-time string creation to avoid runtime allocation. - Use `QStringLiteral("...")` for compile-time string creation to avoid runtime allocation.
- Use `QLatin1String` for comparison with ASCII literals (in Qt 5). - Use `QLatin1String` for comparison with ASCII literals (in Qt 5).
- Prefer `arg()` for formatting (or `QStringBuilder`'s `%` operator). - Prefer `arg()` for formatting (or `QStringBuilder`'s `%` operator).
@@ -99,14 +107,15 @@ if (str == u"test"_s) ... // Qt 6
``` ```
### Container Selection ### Container Selection
- **Qt 6**: `QList` is now the default choice (unified with `QVector`). - **Qt 6**: `QList` is now the default choice (unified with `QVector`).
- **Qt 5**: Prefer `QVector` over `QList` for contiguous memory and cache performance, unless stable references are needed. - **Qt 5**: Prefer `QVector` over `QList` for contiguous memory and cache performance, unless stable references are needed.
- Be aware of Implicit Sharing (Copy-on-Write). Passing containers by value is cheap *until* modified. Use `const &` for read-only access. - Be aware of Implicit Sharing (Copy-on-Write). Passing containers by value is cheap _until_ modified. Use `const &` for read-only access.
```cpp ```cpp
// ❌ Forces deep copy if function modifies 'list' // ❌ Forces deep copy if function modifies 'list'
void process(QVector<int> list) { void process(QVector<int> list) {
list[0] = 1; list[0] = 1;
} }
// ✅ Read-only reference // ✅ Read-only reference
@@ -118,12 +127,13 @@ void process(const QVector<int>& list) { ... }
## Threads & Concurrency ## Threads & Concurrency
### Subclassing QThread vs Worker Object ### Subclassing QThread vs Worker Object
Prefer the "Worker Object" pattern over subclassing `QThread` implementation details. Prefer the "Worker Object" pattern over subclassing `QThread` implementation details.
```cpp ```cpp
// ❌ Business logic inside QThread::run() // ❌ Business logic inside QThread::run()
class MyThread : public QThread { class MyThread : public QThread {
void run() override { ... } void run() override { ... }
}; };
// ✅ Worker object moved to thread // ✅ Worker object moved to thread
@@ -135,6 +145,7 @@ thread->start();
``` ```
### GUI Thread Safety ### GUI Thread Safety
**NEVER** access UI widgets (`QWidget` and subclasses) from a background thread. Use signals/slots to communicate updates to the main thread. **NEVER** access UI widgets (`QWidget` and subclasses) from a background thread. Use signals/slots to communicate updates to the main thread.
--- ---
@@ -142,13 +153,17 @@ thread->start();
## GUI & Widgets ## GUI & Widgets
### Logic Separation ### Logic Separation
Keep business logic out of UI classes (`MainWindow`, `Dialog`). UI classes should only handle display and user input forwarding. Keep business logic out of UI classes (`MainWindow`, `Dialog`). UI classes should only handle display and user input forwarding.
### Layouts ### Layouts
Avoid fixed sizes (`setGeometry`, `resize`). Use layouts (`QVBoxLayout`, `QGridLayout`) to handle different DPIs and window resizing gracefully. Avoid fixed sizes (`setGeometry`, `resize`). Use layouts (`QVBoxLayout`, `QGridLayout`) to handle different DPIs and window resizing gracefully.
### Blocking Event Loop ### Blocking Event Loop
Never execute long-running operations on the main thread (freezes GUI). Never execute long-running operations on the main thread (freezes GUI).
- **Bad**: `Sleep()`, `while(busy)`, synchronous network calls. - **Bad**: `Sleep()`, `while(busy)`, synchronous network calls.
- **Good**: `QProcess`, `QThread`, `QtConcurrent`, or asynchronous APIs (`QNetworkAccessManager`). - **Good**: `QProcess`, `QThread`, `QtConcurrent`, or asynchronous APIs (`QNetworkAccessManager`).
@@ -157,6 +172,7 @@ Never execute long-running operations on the main thread (freezes GUI).
## Meta-Object System ## Meta-Object System
### Properties & Enums ### Properties & Enums
Use `Q_PROPERTY` for values exposed to QML or needing introspection. Use `Q_PROPERTY` for values exposed to QML or needing introspection.
Use `Q_ENUM` to enable string conversion for enums. Use `Q_ENUM` to enable string conversion for enums.
@@ -172,6 +188,7 @@ public:
``` ```
### qobject_cast ### qobject_cast
Use `qobject_cast<T*>` for QObjects instead of `dynamic_cast`. It is faster and doesn't require RTTI. Use `qobject_cast<T*>` for QObjects instead of `dynamic_cast`. It is faster and doesn't require RTTI.
--- ---
@@ -183,4 +200,4 @@ Use `qobject_cast<T*>` for QObjects instead of `dynamic_cast`. It is faster and
- [ ] **Threads**: Is UI accessed only from main thread? Are long tasks offloaded? - [ ] **Threads**: Is UI accessed only from main thread? Are long tasks offloaded?
- [ ] **Strings**: Are `QStringLiteral` or `tr()` used appropriately? - [ ] **Strings**: Are `QStringLiteral` or `tr()` used appropriately?
- [ ] **Style**: Naming conventions (camelCase for methods, PascalCase for classes). - [ ] **Style**: Naming conventions (camelCase for methods, PascalCase for classes).
- [ ] **Resources**: Are resources (images, styles) loaded from `.qrc`? - [ ] **Resources**: Are resources (images, styles) loaded from `.qrc`?
@@ -23,7 +23,7 @@ React 审查重点:Hooks 规则、性能优化的适度性、组件设计、
// ❌ 条件调用 Hooks — 违反 Hooks 规则 // ❌ 条件调用 Hooks — 违反 Hooks 规则
function BadComponent({ isLoggedIn }) { function BadComponent({ isLoggedIn }) {
if (isLoggedIn) { if (isLoggedIn) {
const [user, setUser] = useState(null); // Error! const [user, setUser] = useState(null); // Error!
} }
return <div>...</div>; return <div>...</div>;
} }
@@ -46,7 +46,7 @@ function BadEffect({ userId }) {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
fetchUser(userId).then(setUser); fetchUser(userId).then(setUser);
}, []); // 缺少 userId 依赖! }, []); // 缺少 userId 依赖!
} }
// ✅ 完整的依赖数组 // ✅ 完整的依赖数组
@@ -54,10 +54,12 @@ function GoodEffect({ userId }) {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
fetchUser(userId).then(data => { fetchUser(userId).then((data) => {
if (!cancelled) setUser(data); if (!cancelled) setUser(data);
}); });
return () => { cancelled = true; }; // 清理函数 return () => {
cancelled = true;
}; // 清理函数
}, [userId]); }, [userId]);
} }
@@ -65,17 +67,14 @@ function GoodEffect({ userId }) {
function BadDerived({ items }) { function BadDerived({ items }) {
const [filteredItems, setFilteredItems] = useState([]); const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => { useEffect(() => {
setFilteredItems(items.filter(i => i.active)); setFilteredItems(items.filter((i) => i.active));
}, [items]); // 不必要的 effect + 额外渲染 }, [items]); // 不必要的 effect + 额外渲染
return <List items={filteredItems} />; return <List items={filteredItems} />;
} }
// ✅ 直接在渲染时计算,或用 useMemo // ✅ 直接在渲染时计算,或用 useMemo
function GoodDerived({ items }) { function GoodDerived({ items }) {
const filteredItems = useMemo( const filteredItems = useMemo(() => items.filter((i) => i.active), [items]);
() => items.filter(i => i.active),
[items]
);
return <List items={filteredItems} />; return <List items={filteredItems} />;
} }
@@ -84,7 +83,7 @@ function BadEventEffect() {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
useEffect(() => { useEffect(() => {
if (query) { if (query) {
analytics.track('search', { query }); // 应该在事件处理器中 analytics.track('search', { query }); // 应该在事件处理器中
} }
}, [query]); }, [query]);
} }
@@ -106,15 +105,15 @@ function GoodEvent() {
```tsx ```tsx
// ❌ 过度优化 — 常量不需要 useMemo // ❌ 过度优化 — 常量不需要 useMemo
function OverOptimized() { function OverOptimized() {
const config = useMemo(() => ({ timeout: 5000 }), []); // 无意义 const config = useMemo(() => ({ timeout: 5000 }), []); // 无意义
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
console.log('clicked'); console.log('clicked');
}, []); // 如果不传给 memo 组件,无意义 }, []); // 如果不传给 memo 组件,无意义
} }
// ✅ 只在需要时优化 // ✅ 只在需要时优化
function ProperlyOptimized() { function ProperlyOptimized() {
const config = { timeout: 5000 }; // 简单对象直接定义 const config = { timeout: 5000 }; // 简单对象直接定义
const handleClick = () => console.log('clicked'); const handleClick = () => console.log('clicked');
} }
@@ -147,7 +146,8 @@ function Parent({ rawItems }) {
```tsx ```tsx
// ❌ 在组件内定义组件 — 每次渲染都创建新组件 // ❌ 在组件内定义组件 — 每次渲染都创建新组件
function BadParent() { function BadParent() {
function ChildComponent() { // 每次渲染都是新函数! function ChildComponent() {
// 每次渲染都是新函数!
return <div>child</div>; return <div>child</div>;
} }
return <ChildComponent />; return <ChildComponent />;
@@ -165,8 +165,8 @@ function GoodParent() {
function BadProps() { function BadProps() {
return ( return (
<MemoizedComponent <MemoizedComponent
style={{ color: 'red' }} // 每次渲染新对象 style={{ color: 'red' }} // 每次渲染新对象
onClick={() => {}} // 每次渲染新函数 onClick={() => {}} // 每次渲染新函数
/> />
); );
} }
@@ -188,7 +188,7 @@ function GoodProps() {
function BadApp() { function BadApp() {
return ( return (
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
<DataComponent /> {/* 错误会导致整个应用崩溃 */} <DataComponent /> {/* 错误会导致整个应用崩溃 */}
</Suspense> </Suspense>
); );
} }
@@ -287,15 +287,13 @@ function NewForm() {
return { success: false, error: e.message }; return { success: false, error: e.message };
} }
}, },
{ success: false, data: null, error: null } { success: false, data: null, error: null },
); );
return ( return (
<form action={formAction}> <form action={formAction}>
<input name="email" /> <input name="email" />
<button disabled={isPending}> <button disabled={isPending}>{isPending ? 'Submitting...' : 'Submit'}</button>
{isPending ? 'Submitting...' : 'Submit'}
</button>
{state.error && <p className="error">{state.error}</p>} {state.error && <p className="error">{state.error}</p>}
</form> </form>
); );
@@ -316,16 +314,12 @@ import { useFormStatus } from 'react-dom';
function SubmitButton() { function SubmitButton() {
const { pending, data, method, action } = useFormStatus(); const { pending, data, method, action } = useFormStatus();
// 注意:必须在 <form> 内部的子组件中使用 // 注意:必须在 <form> 内部的子组件中使用
return ( return <button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>;
<button disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
} }
// ❌ useFormStatus 在 form 同级组件中调用——不工作 // ❌ useFormStatus 在 form 同级组件中调用——不工作
function BadForm() { function BadForm() {
const { pending } = useFormStatus(); // 这里无法获取状态! const { pending } = useFormStatus(); // 这里无法获取状态!
return ( return (
<form action={action}> <form action={action}>
<button disabled={pending}>Submit</button> <button disabled={pending}>Submit</button>
@@ -337,7 +331,7 @@ function BadForm() {
function GoodForm() { function GoodForm() {
return ( return (
<form action={action}> <form action={action}>
<SubmitButton /> {/* useFormStatus 在这里面调用 */} <SubmitButton /> {/* useFormStatus 在这里面调用 */}
</form> </form>
); );
} }
@@ -353,7 +347,7 @@ function SlowLike({ postId, likes }) {
const handleLike = async () => { const handleLike = async () => {
setIsPending(true); setIsPending(true);
const newCount = await likePost(postId); // 等待... const newCount = await likePost(postId); // 等待...
setLikeCount(newCount); setLikeCount(newCount);
setIsPending(false); setIsPending(false);
}; };
@@ -365,13 +359,13 @@ import { useOptimistic } from 'react';
function FastLike({ postId, likes }) { function FastLike({ postId, likes }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic( const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes, likes,
(currentLikes, increment: number) => currentLikes + increment (currentLikes, increment: number) => currentLikes + increment,
); );
const handleLike = async () => { const handleLike = async () => {
addOptimisticLike(1); // 立即更新 UI addOptimisticLike(1); // 立即更新 UI
try { try {
await likePost(postId); // 后台同步 await likePost(postId); // 后台同步
} catch { } catch {
// React 自动回滚到 likes 原值 // React 自动回滚到 likes 原值
} }
@@ -398,7 +392,7 @@ function ClientForm() {
// ✅ Server Action + useActionState // ✅ Server Action + useActionState
// actions.ts // actions.ts
'use server'; ('use server');
export async function createPost(prevState: any, formData: FormData) { export async function createPost(prevState: any, formData: FormData) {
const title = formData.get('title'); const title = formData.get('title');
await db.posts.create({ title }); await db.posts.create({ title });
@@ -407,7 +401,7 @@ export async function createPost(prevState: any, formData: FormData) {
} }
// form.tsx // form.tsx
'use client'; ('use client');
import { createPost } from './actions'; import { createPost } from './actions';
function PostForm() { function PostForm() {
@@ -436,7 +430,9 @@ function OldComponent() {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useEffect(() => {
fetchData().then(setData).finally(() => setIsLoading(false)); fetchData()
.then(setData)
.finally(() => setIsLoading(false));
}, []); }, []);
if (isLoading) return <Spinner />; if (isLoading) return <Spinner />;
@@ -447,7 +443,7 @@ function OldComponent() {
function NewComponent() { function NewComponent() {
return ( return (
<Suspense fallback={<Spinner />}> <Suspense fallback={<Spinner />}>
<DataView /> {/* 内部使用 use() 或支持 Suspense 的数据获取 */} <DataView /> {/* 内部使用 use() 或支持 Suspense 的数据获取 */}
</Suspense> </Suspense>
); );
} }
@@ -461,8 +457,8 @@ function BadLayout() {
return ( return (
<Suspense fallback={<FullPageSpinner />}> <Suspense fallback={<FullPageSpinner />}>
<Header /> <Header />
<MainContent /> {/* 慢 */} <MainContent /> {/* 慢 */}
<Sidebar /> {/* 快 */} <Sidebar /> {/* 快 */}
</Suspense> </Suspense>
); );
} }
@@ -471,13 +467,13 @@ function BadLayout() {
function GoodLayout() { function GoodLayout() {
return ( return (
<> <>
<Header /> {/* 立即显示 */} <Header /> {/* 立即显示 */}
<div className="flex"> <div className="flex">
<Suspense fallback={<ContentSkeleton />}> <Suspense fallback={<ContentSkeleton />}>
<MainContent /> {/* 独立加载 */} <MainContent /> {/* 独立加载 */}
</Suspense> </Suspense>
<Suspense fallback={<SidebarSkeleton />}> <Suspense fallback={<SidebarSkeleton />}>
<Sidebar /> {/* 独立加载 */} <Sidebar /> {/* 独立加载 */}
</Suspense> </Suspense>
</div> </div>
</> </>
@@ -508,17 +504,19 @@ export default function Loading() {
import { use } from 'react'; import { use } from 'react';
function Comments({ commentsPromise }) { function Comments({ commentsPromise }) {
const comments = use(commentsPromise); // 自动触发 Suspense const comments = use(commentsPromise); // 自动触发 Suspense
return ( return (
<ul> <ul>
{comments.map(c => <li key={c.id}>{c.text}</li>)} {comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul> </ul>
); );
} }
// 父组件创建 Promise,子组件消费 // 父组件创建 Promise,子组件消费
function Post({ postId }) { function Post({ postId }) {
const commentsPromise = fetchComments(postId); // 不 await const commentsPromise = fetchComments(postId); // 不 await
return ( return (
<article> <article>
<PostContent id={postId} /> <PostContent id={postId} />
@@ -540,16 +538,16 @@ TanStack Query 是 React 生态中最流行的数据获取库,v5 是当前稳
```tsx ```tsx
// ❌ 不正确的默认配置 // ❌ 不正确的默认配置
const queryClient = new QueryClient(); // 默认配置可能不适合 const queryClient = new QueryClient(); // 默认配置可能不适合
// ✅ 生产环境推荐配置 // ✅ 生产环境推荐配置
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
staleTime: 1000 * 60 * 5, // 5 分钟内数据视为新鲜 staleTime: 1000 * 60 * 5, // 5 分钟内数据视为新鲜
gcTime: 1000 * 60 * 30, // 30 分钟后垃圾回收(v5 重命名) gcTime: 1000 * 60 * 30, // 30 分钟后垃圾回收(v5 重命名)
retry: 3, retry: 3,
refetchOnWindowFocus: false, // 根据需求决定 refetchOnWindowFocus: false, // 根据需求决定
}, },
}, },
}); });
@@ -568,8 +566,8 @@ function Component1() {
function prefetchUser(queryClient, userId) { function prefetchUser(queryClient, userId) {
queryClient.prefetchQuery({ queryClient.prefetchQuery({
queryKey: ['users', userId], // 重复! queryKey: ['users', userId], // 重复!
queryFn: () => fetchUser(userId), // 重复! queryFn: () => fetchUser(userId), // 重复!
}); });
} }
@@ -608,21 +606,21 @@ useQuery({
useQuery({ useQuery({
queryKey: ['data'], queryKey: ['data'],
queryFn: fetchData, queryFn: fetchData,
staleTime: 1000 * 60, // 1 分钟内不会重新请求 staleTime: 1000 * 60, // 1 分钟内不会重新请求
}); });
// ❌ 在 queryFn 中使用不稳定的引用 // ❌ 在 queryFn 中使用不稳定的引用
function BadQuery({ filters }) { function BadQuery({ filters }) {
useQuery({ useQuery({
queryKey: ['items'], // queryKey 没有包含 filters queryKey: ['items'], // queryKey 没有包含 filters
queryFn: () => fetchItems(filters), // filters 变化不会触发重新请求 queryFn: () => fetchItems(filters), // filters 变化不会触发重新请求
}); });
} }
// ✅ queryKey 包含所有影响数据的参数 // ✅ queryKey 包含所有影响数据的参数
function GoodQuery({ filters }) { function GoodQuery({ filters }) {
useQuery({ useQuery({
queryKey: ['items', filters], // filters 是 queryKey 的一部分 queryKey: ['items', filters], // filters 是 queryKey 的一部分
queryFn: () => fetchItems(filters), queryFn: () => fetchItems(filters),
}); });
} }
@@ -634,13 +632,13 @@ function GoodQuery({ filters }) {
#### useSuspenseQuery 的限制 #### useSuspenseQuery 的限制
| 特性 | useQuery | useSuspenseQuery | | 特性 | useQuery | useSuspenseQuery |
|------|----------|------------------| | ----------------- | ---------------- | --------------------- |
| `enabled` 选项 | ✅ 支持 | ❌ 不支持 | | `enabled` 选项 | ✅ 支持 | ❌ 不支持 |
| `placeholderData` | ✅ 支持 | ❌ 不支持 | | `placeholderData` | ✅ 支持 | ❌ 不支持 |
| `data` 类型 | `T \| undefined` | `T`(保证有值)| | `data` 类型 | `T \| undefined` | `T`(保证有值) |
| 错误处理 | `error` 属性 | 抛出到 Error Boundary | | 错误处理 | `error` 属性 | 抛出到 Error Boundary |
| 加载状态 | `isLoading` 属性 | 挂起到 Suspense | | 加载状态 | `isLoading` 属性 | 挂起到 Suspense |
#### 不支持 enabled 的替代方案 #### 不支持 enabled 的替代方案
@@ -650,7 +648,7 @@ function BadSuspenseQuery({ userId }) {
const { data } = useSuspenseQuery({ const { data } = useSuspenseQuery({
queryKey: ['user', userId], queryKey: ['user', userId],
queryFn: () => fetchUser(userId), queryFn: () => fetchUser(userId),
enabled: !!userId, // useSuspenseQuery 不支持 enabled enabled: !!userId, // useSuspenseQuery 不支持 enabled
}); });
} }
@@ -764,7 +762,9 @@ function TodoList() {
return ( return (
<ul> <ul>
{todos?.map(todo => <TodoItem key={todo.id} todo={todo} />)} {todos?.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}
{/* 乐观显示正在添加的 todo */} {/* 乐观显示正在添加的 todo */}
{isPending && <TodoItem todo={variables} isOptimistic />} {isPending && <TodoItem todo={variables} isOptimistic />}
</ul> </ul>
@@ -867,5 +867,5 @@ if (isLoading) return <Spinner />; // 首次加载中
- [ ] 使用 @testing-library/react - [ ] 使用 @testing-library/react
- [ ] 用 screen 查询元素 - [ ] 用 screen 查询元素
- [ ] 用 userEvent 代替 fireEvent - [ ] 用 userEvent 代替 fireEvent
- [ ] 优先使用 *ByRole 查询 - [ ] 优先使用 \*ByRole 查询
- [ ] 测试行为而非实现细节 - [ ] 测试行为而非实现细节
@@ -764,11 +764,13 @@ fn create_handler() -> impl Handler {
### 编译器不能捕获的问题 ### 编译器不能捕获的问题
**业务逻辑正确性** **业务逻辑正确性**
- [ ] 边界条件处理正确 - [ ] 边界条件处理正确
- [ ] 状态机转换完整 - [ ] 状态机转换完整
- [ ] 并发场景下的竞态条件 - [ ] 并发场景下的竞态条件
**API 设计** **API 设计**
- [ ] 公共 API 难以误用 - [ ] 公共 API 难以误用
- [ ] 类型签名清晰表达意图 - [ ] 类型签名清晰表达意图
- [ ] 错误类型粒度合适 - [ ] 错误类型粒度合适
@@ -5,6 +5,7 @@ Security-focused code review checklist based on OWASP Top 10 and best practices.
## Authentication & Authorization ## Authentication & Authorization
### Authentication ### Authentication
- [ ] Passwords hashed with strong algorithm (bcrypt, argon2) - [ ] Passwords hashed with strong algorithm (bcrypt, argon2)
- [ ] Password complexity requirements enforced - [ ] Password complexity requirements enforced
- [ ] Account lockout after failed attempts - [ ] Account lockout after failed attempts
@@ -14,6 +15,7 @@ Security-focused code review checklist based on OWASP Top 10 and best practices.
- [ ] Session timeout implemented - [ ] Session timeout implemented
### Authorization ### Authorization
- [ ] Authorization checks on every request - [ ] Authorization checks on every request
- [ ] Principle of least privilege applied - [ ] Principle of least privilege applied
- [ ] Role-based access control (RBAC) properly implemented - [ ] Role-based access control (RBAC) properly implemented
@@ -22,6 +24,7 @@ Security-focused code review checklist based on OWASP Top 10 and best practices.
- [ ] API endpoints protected appropriately - [ ] API endpoints protected appropriately
### JWT Security ### JWT Security
```typescript ```typescript
// ❌ Insecure JWT configuration // ❌ Insecure JWT configuration
jwt.sign(payload, 'weak-secret'); jwt.sign(payload, 'weak-secret');
@@ -31,23 +34,24 @@ jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'RS256', algorithm: 'RS256',
expiresIn: '15m', expiresIn: '15m',
issuer: 'your-app', issuer: 'your-app',
audience: 'your-api' audience: 'your-api',
}); });
// ❌ Not verifying JWT properly // ❌ Not verifying JWT properly
const decoded = jwt.decode(token); // No signature verification! const decoded = jwt.decode(token); // No signature verification!
// ✅ Verify signature and claims // ✅ Verify signature and claims
const decoded = jwt.verify(token, publicKey, { const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256'], algorithms: ['RS256'],
issuer: 'your-app', issuer: 'your-app',
audience: 'your-api' audience: 'your-api',
}); });
``` ```
## Input Validation ## Input Validation
### SQL Injection Prevention ### SQL Injection Prevention
```python ```python
# ❌ Vulnerable to SQL injection # ❌ Vulnerable to SQL injection
query = f"SELECT * FROM users WHERE id = {user_id}" query = f"SELECT * FROM users WHERE id = {user_id}"
@@ -60,6 +64,7 @@ User.objects.filter(id=user_id)
``` ```
### XSS Prevention ### XSS Prevention
```typescript ```typescript
// ❌ Vulnerable to XSS // ❌ Vulnerable to XSS
element.innerHTML = userInput; element.innerHTML = userInput;
@@ -76,6 +81,7 @@ return <div dangerouslySetInnerHTML={{__html: userInput}} />; // Dangerous!
``` ```
### Command Injection Prevention ### Command Injection Prevention
```python ```python
# ❌ Vulnerable to command injection # ❌ Vulnerable to command injection
os.system(f"convert {filename} output.png") os.system(f"convert {filename} output.png")
@@ -89,6 +95,7 @@ safe_filename = shlex.quote(filename)
``` ```
### Path Traversal Prevention ### Path Traversal Prevention
```typescript ```typescript
// ❌ Vulnerable to path traversal // ❌ Vulnerable to path traversal
const filePath = `./uploads/${req.params.filename}`; const filePath = `./uploads/${req.params.filename}`;
@@ -107,6 +114,7 @@ if (!filePath.startsWith(path.resolve('./uploads'))) {
## Data Protection ## Data Protection
### Sensitive Data Handling ### Sensitive Data Handling
- [ ] No secrets in source code - [ ] No secrets in source code
- [ ] Secrets stored in environment variables or secret manager - [ ] Secrets stored in environment variables or secret manager
- [ ] Sensitive data encrypted at rest - [ ] Sensitive data encrypted at rest
@@ -116,6 +124,7 @@ if (!filePath.startsWith(path.resolve('./uploads'))) {
- [ ] Secure data deletion when required - [ ] Secure data deletion when required
### Configuration Security ### Configuration Security
```yaml ```yaml
# ❌ Secrets in config files # ❌ Secrets in config files
database: database:
@@ -127,6 +136,7 @@ database:
``` ```
### Error Messages ### Error Messages
```typescript ```typescript
// ❌ Leaking sensitive information // ❌ Leaking sensitive information
catch (error) { catch (error) {
@@ -148,45 +158,53 @@ catch (error) {
## API Security ## API Security
### Rate Limiting ### Rate Limiting
- [ ] Rate limiting on all public endpoints - [ ] Rate limiting on all public endpoints
- [ ] Stricter limits on authentication endpoints - [ ] Stricter limits on authentication endpoints
- [ ] Per-user and per-IP limits - [ ] Per-user and per-IP limits
- [ ] Graceful handling when limits exceeded - [ ] Graceful handling when limits exceeded
### CORS Configuration ### CORS Configuration
```typescript ```typescript
// ❌ Overly permissive CORS // ❌ Overly permissive CORS
app.use(cors({ origin: '*' })); app.use(cors({ origin: '*' }));
// ✅ Restrictive CORS // ✅ Restrictive CORS
app.use(cors({ app.use(
origin: ['https://your-app.com'], cors({
methods: ['GET', 'POST'], origin: ['https://your-app.com'],
credentials: true methods: ['GET', 'POST'],
})); credentials: true,
}),
);
``` ```
### HTTP Headers ### HTTP Headers
```typescript ```typescript
// Security headers to set // Security headers to set
app.use(helmet({ app.use(
contentSecurityPolicy: { helmet({
directives: { contentSecurityPolicy: {
defaultSrc: ["'self'"], directives: {
scriptSrc: ["'self'"], defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"],
} styleSrc: ["'self'", "'unsafe-inline'"],
}, },
hsts: { maxAge: 31536000, includeSubDomains: true }, },
noSniff: true, hsts: { maxAge: 31536000, includeSubDomains: true },
xssFilter: true, noSniff: true,
frameguard: { action: 'deny' } xssFilter: true,
})); frameguard: { action: 'deny' },
}),
);
``` ```
## Cryptography ## Cryptography
### Secure Practices ### Secure Practices
- [ ] Using well-established algorithms (AES-256, RSA-2048+) - [ ] Using well-established algorithms (AES-256, RSA-2048+)
- [ ] Not implementing custom cryptography - [ ] Not implementing custom cryptography
- [ ] Using cryptographically secure random number generation - [ ] Using cryptographically secure random number generation
@@ -194,6 +212,7 @@ app.use(helmet({
- [ ] Secure key storage (HSM, KMS) - [ ] Secure key storage (HSM, KMS)
### Common Mistakes ### Common Mistakes
```typescript ```typescript
// ❌ Weak random generation // ❌ Weak random generation
const token = Math.random().toString(36); const token = Math.random().toString(36);
@@ -213,6 +232,7 @@ const hash = await bcrypt.hash(password, 12);
## Dependency Security ## Dependency Security
### Checklist ### Checklist
- [ ] Dependencies from trusted sources only - [ ] Dependencies from trusted sources only
- [ ] No known vulnerabilities (npm audit, cargo audit) - [ ] No known vulnerabilities (npm audit, cargo audit)
- [ ] Dependencies kept up to date - [ ] Dependencies kept up to date
@@ -221,6 +241,7 @@ const hash = await bcrypt.hash(password, 12);
- [ ] License compliance verified - [ ] License compliance verified
### Audit Commands ### Audit Commands
```bash ```bash
# Node.js # Node.js
npm audit npm audit
@@ -240,6 +261,7 @@ snyk test
## Logging & Monitoring ## Logging & Monitoring
### Secure Logging ### Secure Logging
- [ ] No sensitive data in logs (passwords, tokens, PII) - [ ] No sensitive data in logs (passwords, tokens, PII)
- [ ] Logs protected from tampering - [ ] Logs protected from tampering
- [ ] Appropriate log retention - [ ] Appropriate log retention
@@ -256,10 +278,10 @@ logger.info('User login attempt', { email, success: true });
## Security Review Severity Levels ## Security Review Severity Levels
| Severity | Description | Action | | Severity | Description | Action |
|----------|-------------|--------| | ------------ | ------------------------------------------------------- | ----------------------------------- |
| **Critical** | Immediate exploitation possible, data breach risk | Block merge, fix immediately | | **Critical** | Immediate exploitation possible, data breach risk | Block merge, fix immediately |
| **High** | Significant vulnerability, requires specific conditions | Block merge, fix before release | | **High** | Significant vulnerability, requires specific conditions | Block merge, fix before release |
| **Medium** | Moderate risk, defense in depth concern | Should fix, can merge with tracking | | **Medium** | Moderate risk, defense in depth concern | Should fix, can merge with tracking |
| **Low** | Minor issue, best practice violation | Nice to fix, non-blocking | | **Low** | Minor issue, best practice violation | Nice to fix, non-blocking |
| **Info** | Suggestion for improvement | Optional enhancement | | **Info** | Suggestion for improvement | Optional enhancement |
@@ -22,7 +22,7 @@
```typescript ```typescript
// ❌ Using any defeats type safety // ❌ Using any defeats type safety
function processData(data: any) { function processData(data: any) {
return data.value; // 无类型检查,运行时可能崩溃 return data.value; // 无类型检查,运行时可能崩溃
} }
// ✅ Use proper types // ✅ Use proper types
@@ -47,7 +47,7 @@ function processUnknown(data: unknown) {
```typescript ```typescript
// ❌ 不安全的类型断言 // ❌ 不安全的类型断言
function getLength(value: string | string[]) { function getLength(value: string | string[]) {
return (value as string[]).length; // 如果是 string 会出错 return (value as string[]).length; // 如果是 string 会出错
} }
// ✅ 使用类型守卫 // ✅ 使用类型守卫
@@ -59,8 +59,12 @@ function getLength(value: string | string[]): number {
} }
// ✅ 使用 in 操作符 // ✅ 使用 in 操作符
interface Dog { bark(): void } interface Dog {
interface Cat { meow(): void } bark(): void;
}
interface Cat {
meow(): void;
}
function speak(animal: Dog | Cat) { function speak(animal: Dog | Cat) {
if ('bark' in animal) { if ('bark' in animal) {
@@ -117,7 +121,7 @@ function getFirst<T>(arr: T[]): T | undefined {
```typescript ```typescript
// ❌ 泛型没有约束,无法访问属性 // ❌ 泛型没有约束,无法访问属性
function getProperty<T>(obj: T, key: string) { function getProperty<T>(obj: T, key: string) {
return obj[key]; // Error: 无法索引 return obj[key]; // Error: 无法索引
} }
// ✅ 使用 keyof 约束 // ✅ 使用 keyof 约束
@@ -126,9 +130,9 @@ function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
} }
const user = { name: 'Alice', age: 30 }; const user = { name: 'Alice', age: 30 };
getProperty(user, 'name'); // 返回类型是 string getProperty(user, 'name'); // 返回类型是 string
getProperty(user, 'age'); // 返回类型是 number getProperty(user, 'age'); // 返回类型是 number
getProperty(user, 'foo'); // Error: 'foo' 不在 keyof User getProperty(user, 'foo'); // Error: 'foo' 不在 keyof User
``` ```
### 泛型默认值 ### 泛型默认值
@@ -157,13 +161,13 @@ interface User {
email: string; email: string;
} }
type PartialUser = Partial<User>; // 所有属性可选 type PartialUser = Partial<User>; // 所有属性可选
type RequiredUser = Required<User>; // 所有属性必需 type RequiredUser = Required<User>; // 所有属性必需
type ReadonlyUser = Readonly<User>; // 所有属性只读 type ReadonlyUser = Readonly<User>; // 所有属性只读
type UserKeys = keyof User; // 'id' | 'name' | 'email' type UserKeys = keyof User; // 'id' | 'name' | 'email'
type NameOnly = Pick<User, 'name'>; // { name: string } type NameOnly = Pick<User, 'name'>; // { name: string }
type WithoutId = Omit<User, 'id'>; // { name: string; email: string } type WithoutId = Omit<User, 'id'>; // { name: string; email: string }
type UserRecord = Record<string, User>; // { [key: string]: User } type UserRecord = Record<string, User>; // { [key: string]: User }
``` ```
--- ---
@@ -176,13 +180,13 @@ type UserRecord = Record<string, User>; // { [key: string]: User }
// ✅ 根据输入类型返回不同类型 // ✅ 根据输入类型返回不同类型
type IsString<T> = T extends string ? true : false; type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true type A = IsString<string>; // true
type B = IsString<number>; // false type B = IsString<number>; // false
// ✅ 提取数组元素类型 // ✅ 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never; type ElementType<T> = T extends (infer U)[] ? U : never;
type Elem = ElementType<string[]>; // string type Elem = ElementType<string[]>; // string
// ✅ 提取函数返回类型(内置 ReturnType) // ✅ 提取函数返回类型(内置 ReturnType)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
@@ -223,23 +227,21 @@ type HandlerName = `on${Capitalize<EventName>}`;
// ✅ API 路由类型 // ✅ API 路由类型
type ApiRoute = `/api/${string}`; type ApiRoute = `/api/${string}`;
const route: ApiRoute = '/api/users'; // OK const route: ApiRoute = '/api/users'; // OK
const badRoute: ApiRoute = '/users'; // Error const badRoute: ApiRoute = '/users'; // Error
``` ```
### Discriminated Unions ### Discriminated Unions
```typescript ```typescript
// ✅ 使用判别属性实现类型安全 // ✅ 使用判别属性实现类型安全
type Result<T, E> = type Result<T, E> = { success: true; data: T } | { success: false; error: E };
| { success: true; data: T }
| { success: false; error: E };
function handleResult(result: Result<User, Error>) { function handleResult(result: Result<User, Error>) {
if (result.success) { if (result.success) {
console.log(result.data.name); // TypeScript 知道 data 存在 console.log(result.data.name); // TypeScript 知道 data 存在
} else { } else {
console.log(result.error.message); // TypeScript 知道 error 存在 console.log(result.error.message); // TypeScript 知道 error 存在
} }
} }
@@ -252,11 +254,11 @@ type Action =
function reducer(state: number, action: Action): number { function reducer(state: number, action: Action): number {
switch (action.type) { switch (action.type) {
case 'INCREMENT': case 'INCREMENT':
return state + action.payload; // payload 类型已知 return state + action.payload; // payload 类型已知
case 'DECREMENT': case 'DECREMENT':
return state - action.payload; return state - action.payload;
case 'RESET': case 'RESET':
return 0; // 这里没有 payload return 0; // 这里没有 payload
} }
} }
``` ```
@@ -296,10 +298,10 @@ function reducer(state: number, action: Action): number {
// tsconfig: "noUncheckedIndexedAccess": true // tsconfig: "noUncheckedIndexedAccess": true
const arr = [1, 2, 3]; const arr = [1, 2, 3];
const first = arr[0]; // 类型是 number | undefined const first = arr[0]; // 类型是 number | undefined
// ❌ 直接使用可能出错 // ❌ 直接使用可能出错
console.log(first.toFixed(2)); // Error: 可能是 undefined console.log(first.toFixed(2)); // Error: 可能是 undefined
// ✅ 先检查 // ✅ 先检查
if (first !== undefined) { if (first !== undefined) {
@@ -320,7 +322,7 @@ console.log(arr[0]!.toFixed(2));
// ❌ Not handling async errors // ❌ Not handling async errors
async function fetchUser(id: string) { async function fetchUser(id: string) {
const response = await fetch(`/api/users/${id}`); const response = await fetch(`/api/users/${id}`);
return response.json(); // 网络错误未处理 return response.json(); // 网络错误未处理
} }
// ✅ Handle errors properly // ✅ Handle errors properly
@@ -346,7 +348,7 @@ async function fetchUser(id: string): Promise<User> {
// ❌ Promise.all 一个失败全部失败 // ❌ Promise.all 一个失败全部失败
async function fetchAllUsers(ids: string[]) { async function fetchAllUsers(ids: string[]) {
const users = await Promise.all(ids.map(fetchUser)); const users = await Promise.all(ids.map(fetchUser));
return users; // 一个失败就全部失败 return users; // 一个失败就全部失败
} }
// ✅ Promise.allSettled 获取所有结果 // ✅ Promise.allSettled 获取所有结果
@@ -378,8 +380,8 @@ function useSearch() {
useEffect(() => { useEffect(() => {
fetch(`/api/search?q=${query}`) fetch(`/api/search?q=${query}`)
.then(r => r.json()) .then((r) => r.json())
.then(setResults); // 旧请求可能后返回! .then(setResults); // 旧请求可能后返回!
}, [query]); }, [query]);
} }
@@ -392,9 +394,9 @@ function useSearch() {
const controller = new AbortController(); const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal }) fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(r => r.json()) .then((r) => r.json())
.then(setResults) .then(setResults)
.catch(e => { .catch((e) => {
if (e.name !== 'AbortError') throw e; if (e.name !== 'AbortError') throw e;
}); });
@@ -412,7 +414,7 @@ function useSearch() {
```typescript ```typescript
// ❌ 可变参数可能被意外修改 // ❌ 可变参数可能被意外修改
function processUsers(users: User[]) { function processUsers(users: User[]) {
users.sort((a, b) => a.name.localeCompare(b.name)); // 修改了原数组! users.sort((a, b) => a.name.localeCompare(b.name)); // 修改了原数组!
return users; return users;
} }
@@ -452,7 +454,7 @@ module.exports = {
'eslint:recommended', 'eslint:recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict' 'plugin:@typescript-eslint/strict',
], ],
rules: { rules: {
// ✅ 类型安全 // ✅ 类型安全
@@ -471,8 +473,8 @@ module.exports = {
// ✅ 代码风格 // ✅ 代码风格
'@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error' '@typescript-eslint/prefer-optional-chain': 'error',
} },
}; };
``` ```
@@ -509,6 +511,7 @@ await Promise.all(items.map(processItem));
## Review Checklist ## Review Checklist
### 类型系统 ### 类型系统
- [ ] 没有使用 `any`(使用 `unknown` + 类型守卫代替) - [ ] 没有使用 `any`(使用 `unknown` + 类型守卫代替)
- [ ] 接口和类型定义完整且有意义的命名 - [ ] 接口和类型定义完整且有意义的命名
- [ ] 使用泛型提高代码复用性 - [ ] 使用泛型提高代码复用性
@@ -516,16 +519,19 @@ await Promise.all(items.map(processItem));
- [ ] 善用工具类型(Partial、Pick、Omit 等) - [ ] 善用工具类型(Partial、Pick、Omit 等)
### 泛型 ### 泛型
- [ ] 泛型有适当的约束(extends) - [ ] 泛型有适当的约束(extends)
- [ ] 泛型参数有合理的默认值 - [ ] 泛型参数有合理的默认值
- [ ] 避免过度泛型化(KISS 原则) - [ ] 避免过度泛型化(KISS 原则)
### Strict 模式 ### Strict 模式
- [ ] tsconfig.json 启用了 strict: true - [ ] tsconfig.json 启用了 strict: true
- [ ] 启用了 noUncheckedIndexedAccess - [ ] 启用了 noUncheckedIndexedAccess
- [ ] 没有使用 @ts-ignore(改用 @ts-expect-error - [ ] 没有使用 @ts-ignore(改用 @ts-expect-error
### 异步代码 ### 异步代码
- [ ] async 函数有错误处理 - [ ] async 函数有错误处理
- [ ] Promise rejection 被正确处理 - [ ] Promise rejection 被正确处理
- [ ] 没有 floating promises(未处理的 Promise - [ ] 没有 floating promises(未处理的 Promise
@@ -533,11 +539,13 @@ await Promise.all(items.map(processItem));
- [ ] 竞态条件使用 AbortController 处理 - [ ] 竞态条件使用 AbortController 处理
### 不可变性 ### 不可变性
- [ ] 不直接修改函数参数 - [ ] 不直接修改函数参数
- [ ] 使用 spread 操作符创建新对象/数组 - [ ] 使用 spread 操作符创建新对象/数组
- [ ] 考虑使用 readonly 修饰符 - [ ] 考虑使用 readonly 修饰符
### ESLint ### ESLint
- [ ] 使用 @typescript-eslint/recommended - [ ] 使用 @typescript-eslint/recommended
- [ ] 没有 ESLint 警告或错误 - [ ] 没有 ESLint 警告或错误
- [ ] 使用 consistent-type-imports - [ ] 使用 consistent-type-imports
@@ -22,11 +22,11 @@
```vue ```vue
<!-- ✅ 基本类型用 ref --> <!-- ✅ 基本类型用 ref -->
<script setup lang="ts"> <script setup lang="ts">
const count = ref(0) const count = ref(0);
const name = ref('Vue') const name = ref('Vue');
// ref 需要 .value 访问 // ref 需要 .value 访问
count.value++ count.value++;
</script> </script>
<!-- ✅ 对象/数组用 reactive(可选)--> <!-- ✅ 对象/数组用 reactive(可选)-->
@@ -34,18 +34,18 @@ count.value++
const state = reactive({ const state = reactive({
user: null, user: null,
loading: false, loading: false,
error: null error: null,
}) });
// reactive 直接访问 // reactive 直接访问
state.loading = true state.loading = true;
</script> </script>
<!-- 💡 现代最佳实践:全部使用 ref,保持一致性 --> <!-- 💡 现代最佳实践:全部使用 ref,保持一致性 -->
<script setup lang="ts"> <script setup lang="ts">
const user = ref<User | null>(null) const user = ref<User | null>(null);
const loading = ref(false) const loading = ref(false);
const error = ref<Error | null>(null) const error = ref<Error | null>(null);
</script> </script>
``` ```
@@ -54,17 +54,17 @@ const error = ref<Error | null>(null)
```vue ```vue
<!-- ❌ 解构 reactive 会丢失响应性 --> <!-- ❌ 解构 reactive 会丢失响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const state = reactive({ count: 0, name: 'Vue' }) const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = state // 丢失响应性! const { count, name } = state; // 丢失响应性!
</script> </script>
<!-- ✅ 使用 toRefs 保持响应性 --> <!-- ✅ 使用 toRefs 保持响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const state = reactive({ count: 0, name: 'Vue' }) const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state) // 保持响应性 const { count, name } = toRefs(state); // 保持响应性
// 或者直接使用 ref // 或者直接使用 ref
const count = ref(0) const count = ref(0);
const name = ref('Vue') const name = ref('Vue');
</script> </script>
``` ```
@@ -74,21 +74,21 @@ const name = ref('Vue')
<!-- ❌ computed 中产生副作用 --> <!-- ❌ computed 中产生副作用 -->
<script setup lang="ts"> <script setup lang="ts">
const fullName = computed(() => { const fullName = computed(() => {
console.log('Computing...') // 副作用! console.log('Computing...'); // 副作用!
otherRef.value = 'changed' // 修改其他状态! otherRef.value = 'changed'; // 修改其他状态!
return `${firstName.value} ${lastName.value}` return `${firstName.value} ${lastName.value}`;
}) });
</script> </script>
<!-- ✅ computed 只用于派生状态 --> <!-- ✅ computed 只用于派生状态 -->
<script setup lang="ts"> <script setup lang="ts">
const fullName = computed(() => { const fullName = computed(() => {
return `${firstName.value} ${lastName.value}` return `${firstName.value} ${lastName.value}`;
}) });
// 副作用放在 watch 或事件处理中 // 副作用放在 watch 或事件处理中
watch(fullName, (name) => { watch(fullName, (name) => {
console.log('Name changed:', name) console.log('Name changed:', name);
}) });
</script> </script>
``` ```
@@ -97,25 +97,25 @@ watch(fullName, (name) => {
```vue ```vue
<!-- ❌ 大型对象使用 ref 会深度转换 --> <!-- ❌ 大型对象使用 ref 会深度转换 -->
<script setup lang="ts"> <script setup lang="ts">
const largeData = ref(hugeNestedObject) // 深度响应式,性能开销大 const largeData = ref(hugeNestedObject); // 深度响应式,性能开销大
</script> </script>
<!-- ✅ 使用 shallowRef 避免深度转换 --> <!-- ✅ 使用 shallowRef 避免深度转换 -->
<script setup lang="ts"> <script setup lang="ts">
const largeData = shallowRef(hugeNestedObject) const largeData = shallowRef(hugeNestedObject);
// 整体替换才会触发更新 // 整体替换才会触发更新
function updateData(newData) { function updateData(newData) {
largeData.value = newData // ✅ 触发更新 largeData.value = newData; // ✅ 触发更新
} }
// ❌ 修改嵌套属性不会触发更新 // ❌ 修改嵌套属性不会触发更新
// largeData.value.nested.prop = 'new' // largeData.value.nested.prop = 'new'
// 需要手动触发时使用 triggerRef // 需要手动触发时使用 triggerRef
import { triggerRef } from 'vue' import { triggerRef } from 'vue';
largeData.value.nested.prop = 'new' largeData.value.nested.prop = 'new';
triggerRef(largeData) triggerRef(largeData);
</script> </script>
``` ```
@@ -128,17 +128,17 @@ triggerRef(largeData)
```vue ```vue
<!-- ❌ 直接修改 props --> <!-- ❌ 直接修改 props -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ user: User }>() const props = defineProps<{ user: User }>();
props.user.name = 'New Name' // 永远不要直接修改 props props.user.name = 'New Name'; // 永远不要直接修改 props
</script> </script>
<!-- ✅ 使用 emit 通知父组件更新 --> <!-- ✅ 使用 emit 通知父组件更新 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ user: User }>() const props = defineProps<{ user: User }>();
const emit = defineEmits<{ const emit = defineEmits<{
update: [name: string] update: [name: string];
}>() }>();
const updateName = (name: string) => emit('update', name) const updateName = (name: string) => emit('update', name);
</script> </script>
``` ```
@@ -147,20 +147,20 @@ const updateName = (name: string) => emit('update', name)
```vue ```vue
<!-- ❌ defineProps 缺少类型声明 --> <!-- ❌ defineProps 缺少类型声明 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps(['title', 'count']) // 无类型检查 const props = defineProps(['title', 'count']); // 无类型检查
</script> </script>
<!-- ✅ 使用类型声明 + withDefaults --> <!-- ✅ 使用类型声明 + withDefaults -->
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
title: string title: string;
count?: number count?: number;
items?: string[] items?: string[];
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
count: 0, count: 0,
items: () => [] // 对象/数组默认值需要工厂函数 items: () => [], // 对象/数组默认值需要工厂函数
}) });
</script> </script>
``` ```
@@ -169,21 +169,21 @@ const props = withDefaults(defineProps<Props>(), {
```vue ```vue
<!-- ❌ defineEmits 缺少类型 --> <!-- ❌ defineEmits 缺少类型 -->
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits(['update', 'delete']) // 无类型检查 const emit = defineEmits(['update', 'delete']); // 无类型检查
emit('update', someValue) // 参数类型不安全 emit('update', someValue); // 参数类型不安全
</script> </script>
<!-- ✅ 完整的类型定义 --> <!-- ✅ 完整的类型定义 -->
<script setup lang="ts"> <script setup lang="ts">
const emit = defineEmits<{ const emit = defineEmits<{
update: [id: number, value: string] update: [id: number, value: string];
delete: [id: number] delete: [id: number];
'custom-event': [payload: CustomPayload] 'custom-event': [payload: CustomPayload];
}>() }>();
// 现在有完整的类型检查 // 现在有完整的类型检查
emit('update', 1, 'new value') // ✅ emit('update', 1, 'new value'); // ✅
emit('update', 'wrong') // ❌ TypeScript 报错 emit('update', 'wrong'); // ❌ TypeScript 报错
</script> </script>
``` ```
@@ -196,22 +196,25 @@ emit('update', 'wrong') // ❌ TypeScript 报错
```vue ```vue
<!-- Vue 3.5 之前:解构会丢失响应性 --> <!-- Vue 3.5 之前:解构会丢失响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ count: number }>() const props = defineProps<{ count: number }>();
// 需要使用 props.count 或 toRefs // 需要使用 props.count 或 toRefs
</script> </script>
<!-- ✅ Vue 3.5+:解构保持响应性 --> <!-- ✅ Vue 3.5+:解构保持响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const { count, name = 'default' } = defineProps<{ const { count, name = 'default' } = defineProps<{
count: number count: number;
name?: string name?: string;
}>() }>();
// count 和 name 自动保持响应性! // count 和 name 自动保持响应性!
// 可以直接在模板和 watch 中使用 // 可以直接在模板和 watch 中使用
watch(() => count, (newCount) => { watch(
console.log('Count changed:', newCount) () => count,
}) (newCount) => {
console.log('Count changed:', newCount);
},
);
</script> </script>
<!-- ✅ 配合默认值使用 --> <!-- ✅ 配合默认值使用 -->
@@ -219,12 +222,12 @@ watch(() => count, (newCount) => {
const { const {
title, title,
count = 0, count = 0,
items = () => [] // 函数作为默认值(对象/数组) items = () => [], // 函数作为默认值(对象/数组)
} = defineProps<{ } = defineProps<{
title: string title: string;
count?: number count?: number;
items?: () => string[] items?: () => string[];
}>() }>();
</script> </script>
``` ```
@@ -233,23 +236,23 @@ const {
```vue ```vue
<!-- ❌ 传统 v-model 实现:冗长 --> <!-- ❌ 传统 v-model 实现:冗长 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ modelValue: string }>() const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>() const emit = defineEmits<{ 'update:modelValue': [value: string] }>();
// 需要 computed 来双向绑定 // 需要 computed 来双向绑定
const value = computed({ const value = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => emit('update:modelValue', val) set: (val) => emit('update:modelValue', val),
}) });
</script> </script>
<!-- ✅ defineModel:简洁的 v-model 实现 --> <!-- ✅ defineModel:简洁的 v-model 实现 -->
<script setup lang="ts"> <script setup lang="ts">
// 自动处理 props 和 emit // 自动处理 props 和 emit
const model = defineModel<string>() const model = defineModel<string>();
// 直接使用 // 直接使用
model.value = 'new value' // 自动 emit model.value = 'new value'; // 自动 emit
</script> </script>
<template> <template>
<input v-model="model" /> <input v-model="model" />
@@ -258,19 +261,19 @@ model.value = 'new value' // 自动 emit
<!-- ✅ 命名 v-model --> <!-- ✅ 命名 v-model -->
<script setup lang="ts"> <script setup lang="ts">
// v-model:title 的实现 // v-model:title 的实现
const title = defineModel<string>('title') const title = defineModel<string>('title');
// 带默认值和选项 // 带默认值和选项
const count = defineModel<number>('count', { const count = defineModel<number>('count', {
default: 0, default: 0,
required: false required: false,
}) });
</script> </script>
<!-- ✅ 多个 v-model --> <!-- ✅ 多个 v-model -->
<script setup lang="ts"> <script setup lang="ts">
const firstName = defineModel<string>('firstName') const firstName = defineModel<string>('firstName');
const lastName = defineModel<string>('lastName') const lastName = defineModel<string>('lastName');
</script> </script>
<template> <template>
<!-- 父组件使用:<MyInput v-model:first-name="first" v-model:last-name="last" /> --> <!-- 父组件使用:<MyInput v-model:first-name="first" v-model:last-name="last" /> -->
@@ -278,7 +281,7 @@ const lastName = defineModel<string>('lastName')
<!-- ✅ v-model 修饰符 --> <!-- ✅ v-model 修饰符 -->
<script setup lang="ts"> <script setup lang="ts">
const [model, modifiers] = defineModel<string>() const [model, modifiers] = defineModel<string>();
// 检查修饰符 // 检查修饰符
if (modifiers.capitalize) { if (modifiers.capitalize) {
@@ -292,7 +295,7 @@ if (modifiers.capitalize) {
```vue ```vue
<!-- 传统方式:ref 属性与变量同名 --> <!-- 传统方式:ref 属性与变量同名 -->
<script setup lang="ts"> <script setup lang="ts">
const inputRef = ref<HTMLInputElement | null>(null) const inputRef = ref<HTMLInputElement | null>(null);
</script> </script>
<template> <template>
<input ref="inputRef" /> <input ref="inputRef" />
@@ -300,13 +303,13 @@ const inputRef = ref<HTMLInputElement | null>(null)
<!-- ✅ useTemplateRef:更清晰的模板引用 --> <!-- ✅ useTemplateRef:更清晰的模板引用 -->
<script setup lang="ts"> <script setup lang="ts">
import { useTemplateRef } from 'vue' import { useTemplateRef } from 'vue';
const input = useTemplateRef<HTMLInputElement>('my-input') const input = useTemplateRef<HTMLInputElement>('my-input');
onMounted(() => { onMounted(() => {
input.value?.focus() input.value?.focus();
}) });
</script> </script>
<template> <template>
<input ref="my-input" /> <input ref="my-input" />
@@ -314,8 +317,8 @@ onMounted(() => {
<!-- ✅ 动态 ref --> <!-- ✅ 动态 ref -->
<script setup lang="ts"> <script setup lang="ts">
const refKey = ref('input-a') const refKey = ref('input-a');
const dynamicInput = useTemplateRef<HTMLInputElement>(refKey) const dynamicInput = useTemplateRef<HTMLInputElement>(refKey);
</script> </script>
``` ```
@@ -324,14 +327,14 @@ const dynamicInput = useTemplateRef<HTMLInputElement>(refKey)
```vue ```vue
<!-- ❌ 手动生成 ID 可能冲突 --> <!-- ❌ 手动生成 ID 可能冲突 -->
<script setup lang="ts"> <script setup lang="ts">
const id = `input-${Math.random()}` // SSR 不一致! const id = `input-${Math.random()}`; // SSR 不一致!
</script> </script>
<!-- ✅ useIdSSR 安全的唯一 ID --> <!-- ✅ useIdSSR 安全的唯一 ID -->
<script setup lang="ts"> <script setup lang="ts">
import { useId } from 'vue' import { useId } from 'vue';
const id = useId() // 例如:'v-0' const id = useId(); // 例如:'v-0'
</script> </script>
<template> <template>
<label :for="id">Name</label> <label :for="id">Name</label>
@@ -340,15 +343,12 @@ const id = useId() // 例如:'v-0'
<!-- ✅ 表单组件中使用 --> <!-- ✅ 表单组件中使用 -->
<script setup lang="ts"> <script setup lang="ts">
const inputId = useId() const inputId = useId();
const errorId = useId() const errorId = useId();
</script> </script>
<template> <template>
<label :for="inputId">Email</label> <label :for="inputId">Email</label>
<input <input :id="inputId" :aria-describedby="errorId" />
:id="inputId"
:aria-describedby="errorId"
/>
<span :id="errorId" class="error">{{ error }}</span> <span :id="errorId" class="error">{{ error }}</span>
</template> </template>
``` ```
@@ -359,28 +359,28 @@ const errorId = useId()
<!-- 传统方式:watch 第三个参数 --> <!-- 传统方式:watch 第三个参数 -->
<script setup lang="ts"> <script setup lang="ts">
watch(source, async (value, oldValue, onCleanup) => { watch(source, async (value, oldValue, onCleanup) => {
const controller = new AbortController() const controller = new AbortController();
onCleanup(() => controller.abort()) onCleanup(() => controller.abort());
// ... // ...
}) });
</script> </script>
<!-- ✅ onWatcherCleanup:更灵活的清理 --> <!-- ✅ onWatcherCleanup:更灵活的清理 -->
<script setup lang="ts"> <script setup lang="ts">
import { onWatcherCleanup } from 'vue' import { onWatcherCleanup } from 'vue';
watch(source, async (value) => { watch(source, async (value) => {
const controller = new AbortController() const controller = new AbortController();
onWatcherCleanup(() => controller.abort()) onWatcherCleanup(() => controller.abort());
// 可以在任意位置调用,不限于回调开头 // 可以在任意位置调用,不限于回调开头
if (someCondition) { if (someCondition) {
const anotherResource = createResource() const anotherResource = createResource();
onWatcherCleanup(() => anotherResource.dispose()) onWatcherCleanup(() => anotherResource.dispose());
} }
await fetchData(value, controller.signal) await fetchData(value, controller.signal);
}) });
</script> </script>
``` ```
@@ -415,15 +415,15 @@ watch(source, async (value) => {
watch( watch(
() => props.userId, () => props.userId,
async (userId) => { async (userId) => {
user.value = await fetchUser(userId) user.value = await fetchUser(userId);
} },
) );
// ✅ watchEffect:自动收集依赖,立即执行 // ✅ watchEffect:自动收集依赖,立即执行
watchEffect(async () => { watchEffect(async () => {
// 自动追踪 props.userId // 自动追踪 props.userId
user.value = await fetchUser(props.userId) user.value = await fetchUser(props.userId);
}) });
// 💡 选择指南: // 💡 选择指南:
// - 需要旧值?用 watch // - 需要旧值?用 watch
@@ -438,30 +438,30 @@ watchEffect(async () => {
<!-- ❌ watch 缺少清理函数,可能内存泄漏 --> <!-- ❌ watch 缺少清理函数,可能内存泄漏 -->
<script setup lang="ts"> <script setup lang="ts">
watch(searchQuery, async (query) => { watch(searchQuery, async (query) => {
const controller = new AbortController() const controller = new AbortController();
const data = await fetch(`/api/search?q=${query}`, { const data = await fetch(`/api/search?q=${query}`, {
signal: controller.signal signal: controller.signal,
}) });
results.value = await data.json() results.value = await data.json();
// 如果 query 快速变化,旧请求不会被取消! // 如果 query 快速变化,旧请求不会被取消!
}) });
</script> </script>
<!-- ✅ 使用 onCleanup 清理副作用 --> <!-- ✅ 使用 onCleanup 清理副作用 -->
<script setup lang="ts"> <script setup lang="ts">
watch(searchQuery, async (query, _, onCleanup) => { watch(searchQuery, async (query, _, onCleanup) => {
const controller = new AbortController() const controller = new AbortController();
onCleanup(() => controller.abort()) // 取消旧请求 onCleanup(() => controller.abort()); // 取消旧请求
try { try {
const data = await fetch(`/api/search?q=${query}`, { const data = await fetch(`/api/search?q=${query}`, {
signal: controller.signal signal: controller.signal,
}) });
results.value = await data.json() results.value = await data.json();
} catch (e) { } catch (e) {
if (e.name !== 'AbortError') throw e if (e.name !== 'AbortError') throw e;
} }
}) });
</script> </script>
``` ```
@@ -473,19 +473,19 @@ watch(searchQuery, async (query, _, onCleanup) => {
watch( watch(
userId, userId,
async (id) => { async (id) => {
user.value = await fetchUser(id) user.value = await fetchUser(id);
}, },
{ immediate: true } { immediate: true },
) );
// ✅ deep:深度监听(性能开销大,谨慎使用) // ✅ deep:深度监听(性能开销大,谨慎使用)
watch( watch(
state, state,
(newState) => { (newState) => {
console.log('State changed deeply') console.log('State changed deeply');
}, },
{ deep: true } { deep: true },
) );
// ✅ flush: 'post'DOM 更新后执行 // ✅ flush: 'post'DOM 更新后执行
watch( watch(
@@ -494,17 +494,17 @@ watch(
// 可以安全访问更新后的 DOM // 可以安全访问更新后的 DOM
// nextTick 不再需要 // nextTick 不再需要
}, },
{ flush: 'post' } { flush: 'post' },
) );
// ✅ once: true (Vue 3.4+):只执行一次 // ✅ once: true (Vue 3.4+):只执行一次
watch( watch(
source, source,
(value) => { (value) => {
console.log('只会执行一次:', value) console.log('只会执行一次:', value);
}, },
{ once: true } { once: true },
) );
</script> </script>
``` ```
@@ -513,20 +513,17 @@ watch(
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
// ✅ 监听多个 ref // ✅ 监听多个 ref
watch( watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
[firstName, lastName], console.log(`Name changed from ${oldFirst} ${oldLast} to ${newFirst} ${newLast}`);
([newFirst, newLast], [oldFirst, oldLast]) => { });
console.log(`Name changed from ${oldFirst} ${oldLast} to ${newFirst} ${newLast}`)
}
)
// ✅ 监听 reactive 对象的特定属性 // ✅ 监听 reactive 对象的特定属性
watch( watch(
() => [state.count, state.name], () => [state.count, state.name],
([count, name]) => { ([count, name]) => {
console.log(`count: ${count}, name: ${name}`) console.log(`count: ${count}, name: ${name}`);
} },
) );
</script> </script>
``` ```
@@ -571,9 +568,7 @@ watch(
<!-- ✅ 使用 computed 过滤 --> <!-- ✅ 使用 computed 过滤 -->
<script setup lang="ts"> <script setup lang="ts">
const activeUsers = computed(() => const activeUsers = computed(() => users.value.filter((user) => user.active));
users.value.filter(user => user.active)
)
</script> </script>
<template> <template>
<li v-for="user in activeUsers" :key="user.id"> <li v-for="user in activeUsers" :key="user.id">
@@ -596,7 +591,12 @@ const activeUsers = computed(() =>
```vue ```vue
<!-- ❌ 内联复杂逻辑 --> <!-- ❌ 内联复杂逻辑 -->
<template> <template>
<button @click="items = items.filter(i => i.id !== item.id); count--"> <button
@click="
items = items.filter((i) => i.id !== item.id);
count--;
"
>
Delete Delete
</button> </button>
</template> </template>
@@ -604,9 +604,9 @@ const activeUsers = computed(() =>
<!-- ✅ 使用方法 --> <!-- ✅ 使用方法 -->
<script setup lang="ts"> <script setup lang="ts">
const deleteItem = (id: number) => { const deleteItem = (id: number) => {
items.value = items.value.filter(i => i.id !== id) items.value = items.value.filter((i) => i.id !== id);
count.value-- count.value--;
} };
</script> </script>
<template> <template>
<button @click="deleteItem(item.id)">Delete</button> <button @click="deleteItem(item.id)">Delete</button>
@@ -637,27 +637,27 @@ const deleteItem = (id: number) => {
```typescript ```typescript
// ✅ 好的 composable 设计 // ✅ 好的 composable 设计
export function useCounter(initialValue = 0) { export function useCounter(initialValue = 0) {
const count = ref(initialValue) const count = ref(initialValue);
const increment = () => count.value++ const increment = () => count.value++;
const decrement = () => count.value-- const decrement = () => count.value--;
const reset = () => count.value = initialValue const reset = () => (count.value = initialValue);
// 返回响应式引用和方法 // 返回响应式引用和方法
return { return {
count: readonly(count), // 只读防止外部修改 count: readonly(count), // 只读防止外部修改
increment, increment,
decrement, decrement,
reset reset,
} };
} }
// ❌ 不要返回 .value // ❌ 不要返回 .value
export function useBadCounter() { export function useBadCounter() {
const count = ref(0) const count = ref(0);
return { return {
count: count.value // ❌ 丢失响应性! count: count.value, // ❌ 丢失响应性!
} };
} }
``` ```
@@ -666,21 +666,21 @@ export function useBadCounter() {
```vue ```vue
<!-- ❌ 传递 props 到 composable 丢失响应性 --> <!-- ❌ 传递 props 到 composable 丢失响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ userId: string }>() const props = defineProps<{ userId: string }>();
const { user } = useUser(props.userId) // 丢失响应性! const { user } = useUser(props.userId); // 丢失响应性!
</script> </script>
<!-- ✅ 使用 toRef 或 computed 保持响应性 --> <!-- ✅ 使用 toRef 或 computed 保持响应性 -->
<script setup lang="ts"> <script setup lang="ts">
const props = defineProps<{ userId: string }>() const props = defineProps<{ userId: string }>();
const userIdRef = toRef(props, 'userId') const userIdRef = toRef(props, 'userId');
const { user } = useUser(userIdRef) // 保持响应性 const { user } = useUser(userIdRef); // 保持响应性
// 或使用 computed // 或使用 computed
const { user } = useUser(computed(() => props.userId)) const { user } = useUser(computed(() => props.userId));
// ✅ Vue 3.5+:直接解构使用 // ✅ Vue 3.5+:直接解构使用
const { userId } = defineProps<{ userId: string }>() const { userId } = defineProps<{ userId: string }>();
const { user } = useUser(() => userId) // getter 函数 const { user } = useUser(() => userId); // getter 函数
</script> </script>
``` ```
@@ -689,43 +689,43 @@ const { user } = useUser(() => userId) // getter 函数
```typescript ```typescript
// ✅ 异步 composable 模式 // ✅ 异步 composable 模式
export function useFetch<T>(url: MaybeRefOrGetter<string>) { export function useFetch<T>(url: MaybeRefOrGetter<string>) {
const data = ref<T | null>(null) const data = ref<T | null>(null);
const error = ref<Error | null>(null) const error = ref<Error | null>(null);
const loading = ref(false) const loading = ref(false);
const execute = async () => { const execute = async () => {
loading.value = true loading.value = true;
error.value = null error.value = null;
try { try {
const response = await fetch(toValue(url)) const response = await fetch(toValue(url));
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}`) throw new Error(`HTTP ${response.status}`);
} }
data.value = await response.json() data.value = await response.json();
} catch (e) { } catch (e) {
error.value = e as Error error.value = e as Error;
} finally { } finally {
loading.value = false loading.value = false;
} }
} };
// 响应式 URL 时自动重新获取 // 响应式 URL 时自动重新获取
watchEffect(() => { watchEffect(() => {
toValue(url) // 追踪依赖 toValue(url); // 追踪依赖
execute() execute();
}) });
return { return {
data: readonly(data), data: readonly(data),
error: readonly(error), error: readonly(error),
loading: readonly(loading), loading: readonly(loading),
refetch: execute refetch: execute,
} };
} }
// 使用 // 使用
const { data, loading, error, refetch } = useFetch<User[]>('/api/users') const { data, loading, error, refetch } = useFetch<User[]>('/api/users');
``` ```
### 生命周期与清理 ### 生命周期与清理
@@ -735,34 +735,40 @@ const { data, loading, error, refetch } = useFetch<User[]>('/api/users')
export function useEventListener( export function useEventListener(
target: MaybeRefOrGetter<EventTarget>, target: MaybeRefOrGetter<EventTarget>,
event: string, event: string,
handler: EventListener handler: EventListener,
) { ) {
// 组件挂载后添加 // 组件挂载后添加
onMounted(() => { onMounted(() => {
toValue(target).addEventListener(event, handler) toValue(target).addEventListener(event, handler);
}) });
// 组件卸载时移除 // 组件卸载时移除
onUnmounted(() => { onUnmounted(() => {
toValue(target).removeEventListener(event, handler) toValue(target).removeEventListener(event, handler);
}) });
} }
// ✅ 使用 effectScope 管理副作用 // ✅ 使用 effectScope 管理副作用
export function useFeature() { export function useFeature() {
const scope = effectScope() const scope = effectScope();
scope.run(() => { scope.run(() => {
// 所有响应式效果都在这个 scope 内 // 所有响应式效果都在这个 scope 内
const state = ref(0) const state = ref(0);
watch(state, () => { /* ... */ }) watch(state, () => {
watchEffect(() => { /* ... */ }) /* ... */
}) });
watchEffect(() => {
/* ... */
});
});
// 清理所有效果 // 清理所有效果
onUnmounted(() => scope.stop()) onUnmounted(() => scope.stop());
return { /* ... */ } return {
/* ... */
};
} }
``` ```
@@ -783,11 +789,7 @@ export function useFeature() {
<!-- ✅ 配合 v-for 使用 --> <!-- ✅ 配合 v-for 使用 -->
<template> <template>
<div <div v-for="item in list" :key="item.id" v-memo="[item.name, item.status]">
v-for="item in list"
:key="item.id"
v-memo="[item.name, item.status]"
>
<!-- 只有 name 或 status 变化时重新渲染 --> <!-- 只有 name 或 status 变化时重新渲染 -->
</div> </div>
</template> </template>
@@ -797,21 +799,19 @@ export function useFeature() {
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue';
// ✅ 懒加载组件 // ✅ 懒加载组件
const HeavyChart = defineAsyncComponent(() => const HeavyChart = defineAsyncComponent(() => import('./components/HeavyChart.vue'));
import('./components/HeavyChart.vue')
)
// ✅ 带加载和错误状态 // ✅ 带加载和错误状态
const AsyncModal = defineAsyncComponent({ const AsyncModal = defineAsyncComponent({
loader: () => import('./components/Modal.vue'), loader: () => import('./components/Modal.vue'),
loadingComponent: LoadingSpinner, loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay, errorComponent: ErrorDisplay,
delay: 200, // 延迟显示 loading(避免闪烁) delay: 200, // 延迟显示 loading(避免闪烁)
timeout: 3000 // 超时时间 timeout: 3000, // 超时时间
}) });
</script> </script>
``` ```
@@ -839,13 +839,13 @@ const AsyncModal = defineAsyncComponent({
// KeepAlive 组件的生命周期钩子 // KeepAlive 组件的生命周期钩子
onActivated(() => { onActivated(() => {
// 组件被激活时(从缓存恢复) // 组件被激活时(从缓存恢复)
refreshData() refreshData();
}) });
onDeactivated(() => { onDeactivated(() => {
// 组件被停用时(进入缓存) // 组件被停用时(进入缓存)
pauseTimers() pauseTimers();
}) });
</script> </script>
``` ```
@@ -854,12 +854,9 @@ onDeactivated(() => {
```vue ```vue
<!-- ✅ 大型列表使用虚拟滚动 --> <!-- ✅ 大型列表使用虚拟滚动 -->
<script setup lang="ts"> <script setup lang="ts">
import { useVirtualList } from '@vueuse/core' import { useVirtualList } from '@vueuse/core';
const { list, containerProps, wrapperProps } = useVirtualList( const { list, containerProps, wrapperProps } = useVirtualList(items, { itemHeight: 50 });
items,
{ itemHeight: 50 }
)
</script> </script>
<template> <template>
<div v-bind="containerProps" style="height: 400px; overflow: auto"> <div v-bind="containerProps" style="height: 400px; overflow: auto">
@@ -877,6 +874,7 @@ const { list, containerProps, wrapperProps } = useVirtualList(
## Review Checklist ## Review Checklist
### 响应性系统 ### 响应性系统
- [ ] ref 用于基本类型,reactive 用于对象(或统一用 ref) - [ ] ref 用于基本类型,reactive 用于对象(或统一用 ref)
- [ ] 没有解构 reactive 对象(或使用了 toRefs - [ ] 没有解构 reactive 对象(或使用了 toRefs
- [ ] props 传递给 composable 时保持了响应性 - [ ] props 传递给 composable 时保持了响应性
@@ -884,6 +882,7 @@ const { list, containerProps, wrapperProps } = useVirtualList(
- [ ] computed 中没有副作用 - [ ] computed 中没有副作用
### Props & Emits ### Props & Emits
- [ ] defineProps 使用 TypeScript 类型声明 - [ ] defineProps 使用 TypeScript 类型声明
- [ ] 复杂默认值使用 withDefaults + 工厂函数 - [ ] 复杂默认值使用 withDefaults + 工厂函数
- [ ] defineEmits 有完整的类型定义 - [ ] defineEmits 有完整的类型定义
@@ -891,12 +890,14 @@ const { list, containerProps, wrapperProps } = useVirtualList(
- [ ] 考虑使用 defineModel 简化 v-modelVue 3.4+ - [ ] 考虑使用 defineModel 简化 v-modelVue 3.4+
### Vue 3.5 新特性(如适用) ### Vue 3.5 新特性(如适用)
- [ ] 使用 Reactive Props Destructure 简化 props 访问 - [ ] 使用 Reactive Props Destructure 简化 props 访问
- [ ] 使用 useTemplateRef 替代 ref 属性 - [ ] 使用 useTemplateRef 替代 ref 属性
- [ ] 表单使用 useId 生成 SSR 安全的 ID - [ ] 表单使用 useId 生成 SSR 安全的 ID
- [ ] 使用 onWatcherCleanup 处理复杂清理逻辑 - [ ] 使用 onWatcherCleanup 处理复杂清理逻辑
### Watchers ### Watchers
- [ ] watch/watchEffect 有适当的清理函数 - [ ] watch/watchEffect 有适当的清理函数
- [ ] 异步 watch 处理了竞态条件 - [ ] 异步 watch 处理了竞态条件
- [ ] flush: 'post' 用于 DOM 操作的 watcher - [ ] flush: 'post' 用于 DOM 操作的 watcher
@@ -904,12 +905,14 @@ const { list, containerProps, wrapperProps } = useVirtualList(
- [ ] 考虑 once: true 用于一次性监听 - [ ] 考虑 once: true 用于一次性监听
### 模板 ### 模板
- [ ] v-for 使用唯一且稳定的 key - [ ] v-for 使用唯一且稳定的 key
- [ ] v-if 和 v-for 没有在同一元素上 - [ ] v-if 和 v-for 没有在同一元素上
- [ ] 事件处理使用方法而非内联复杂逻辑 - [ ] 事件处理使用方法而非内联复杂逻辑
- [ ] 大型列表使用虚拟滚动 - [ ] 大型列表使用虚拟滚动
### Composables ### Composables
- [ ] 相关逻辑提取到 composables - [ ] 相关逻辑提取到 composables
- [ ] composables 返回响应式引用(不是 .value) - [ ] composables 返回响应式引用(不是 .value)
- [ ] 纯函数不要包装成 composable - [ ] 纯函数不要包装成 composable
@@ -917,6 +920,7 @@ const { list, containerProps, wrapperProps } = useVirtualList(
- [ ] 使用 effectScope 管理复杂副作用 - [ ] 使用 effectScope 管理复杂副作用
### 性能 ### 性能
- [ ] 大型组件拆分为小组件 - [ ] 大型组件拆分为小组件
- [ ] 使用 defineAsyncComponent 懒加载 - [ ] 使用 defineAsyncComponent 懒加载
- [ ] 避免不必要的响应式转换 - [ ] 避免不必要的响应式转换
@@ -39,24 +39,28 @@ Before creating competitor pages, understand:
## Core Principles ## Core Principles
### 1. Honesty Builds Trust ### 1. Honesty Builds Trust
- Acknowledge competitor strengths - Acknowledge competitor strengths
- Be accurate about your limitations - Be accurate about your limitations
- Don't misrepresent competitor features - Don't misrepresent competitor features
- Readers are comparing—they'll verify claims - Readers are comparing—they'll verify claims
### 2. Depth Over Surface ### 2. Depth Over Surface
- Go beyond feature checklists - Go beyond feature checklists
- Explain *why* differences matter - Explain _why_ differences matter
- Include use cases and scenarios - Include use cases and scenarios
- Show, don't just tell - Show, don't just tell
### 3. Help Them Decide ### 3. Help Them Decide
- Different tools fit different needs - Different tools fit different needs
- Be clear about who you're best for - Be clear about who you're best for
- Be clear about who competitor is best for - Be clear about who competitor is best for
- Reduce evaluation friction - Reduce evaluation friction
### 4. Modular Content Architecture ### 4. Modular Content Architecture
- Competitor data should be centralized - Competitor data should be centralized
- Updates propagate to all pages - Updates propagate to all pages
- Single source of truth per competitor - Single source of truth per competitor
@@ -74,6 +78,7 @@ Before creating competitor pages, understand:
**Target keywords**: "[Competitor] alternative", "alternative to [Competitor]", "switch from [Competitor]" **Target keywords**: "[Competitor] alternative", "alternative to [Competitor]", "switch from [Competitor]"
**Page structure**: **Page structure**:
1. Why people look for alternatives (validate their pain) 1. Why people look for alternatives (validate their pain)
2. Summary: You as the alternative (quick positioning) 2. Summary: You as the alternative (quick positioning)
3. Detailed comparison (features, service, pricing) 3. Detailed comparison (features, service, pricing)
@@ -93,6 +98,7 @@ Before creating competitor pages, understand:
**Target keywords**: "[Competitor] alternatives", "best [Competitor] alternatives", "tools like [Competitor]" **Target keywords**: "[Competitor] alternatives", "best [Competitor] alternatives", "tools like [Competitor]"
**Page structure**: **Page structure**:
1. Why people look for alternatives (common pain points) 1. Why people look for alternatives (common pain points)
2. What to look for in an alternative (criteria framework) 2. What to look for in an alternative (criteria framework)
3. List of alternatives (you first, but include real options) 3. List of alternatives (you first, but include real options)
@@ -114,6 +120,7 @@ Before creating competitor pages, understand:
**Target keywords**: "[You] vs [Competitor]", "[Competitor] vs [You]" **Target keywords**: "[You] vs [Competitor]", "[Competitor] vs [You]"
**Page structure**: **Page structure**:
1. TL;DR summary (key differences in 2-3 sentences) 1. TL;DR summary (key differences in 2-3 sentences)
2. At-a-glance comparison table 2. At-a-glance comparison table
3. Detailed comparison by category (Features, Pricing, Support, Ease of use, Integrations) 3. Detailed comparison by category (Features, Pricing, Support, Ease of use, Integrations)
@@ -132,6 +139,7 @@ Before creating competitor pages, understand:
**URL pattern**: `/compare/[competitor-a]-vs-[competitor-b]` **URL pattern**: `/compare/[competitor-a]-vs-[competitor-b]`
**Page structure**: **Page structure**:
1. Overview of both products 1. Overview of both products
2. Comparison by category 2. Comparison by category
3. Who each is best for 3. Who each is best for
@@ -146,21 +154,27 @@ Before creating competitor pages, understand:
## Essential Sections ## Essential Sections
### TL;DR Summary ### TL;DR Summary
Start every page with a quick summary for scanners—key differences in 2-3 sentences. Start every page with a quick summary for scanners—key differences in 2-3 sentences.
### Paragraph Comparisons ### Paragraph Comparisons
Go beyond tables. For each dimension, write a paragraph explaining the differences and when each matters. Go beyond tables. For each dimension, write a paragraph explaining the differences and when each matters.
### Feature Comparison ### Feature Comparison
For each category: describe how each handles it, list strengths and limitations, give bottom line recommendation. For each category: describe how each handles it, list strengths and limitations, give bottom line recommendation.
### Pricing Comparison ### Pricing Comparison
Include tier-by-tier comparison, what's included, hidden costs, and total cost calculation for sample team size. Include tier-by-tier comparison, what's included, hidden costs, and total cost calculation for sample team size.
### Who It's For ### Who It's For
Be explicit about ideal customer for each option. Honest recommendations build trust. Be explicit about ideal customer for each option. Honest recommendations build trust.
### Migration Section ### Migration Section
Cover what transfers, what needs reconfiguration, support offered, and quotes from customers who switched. Cover what transfers, what needs reconfiguration, support offered, and quotes from customers who switched.
**For detailed templates**: See [references/templates.md](references/templates.md) **For detailed templates**: See [references/templates.md](references/templates.md)
@@ -170,7 +184,9 @@ Cover what transfers, what needs reconfiguration, support offered, and quotes fr
## Content Architecture ## Content Architecture
### Centralized Competitor Data ### Centralized Competitor Data
Create a single source of truth for each competitor with: Create a single source of truth for each competitor with:
- Positioning and target audience - Positioning and target audience
- Pricing (all tiers) - Pricing (all tiers)
- Feature ratings - Feature ratings
@@ -207,19 +223,21 @@ For each competitor, gather:
### Keyword Targeting ### Keyword Targeting
| Format | Primary Keywords | | Format | Primary Keywords |
|--------|-----------------| | ------------------------ | --------------------------------------------------------- |
| Alternative (singular) | [Competitor] alternative, alternative to [Competitor] | | Alternative (singular) | [Competitor] alternative, alternative to [Competitor] |
| Alternatives (plural) | [Competitor] alternatives, best [Competitor] alternatives | | Alternatives (plural) | [Competitor] alternatives, best [Competitor] alternatives |
| You vs Competitor | [You] vs [Competitor], [Competitor] vs [You] | | You vs Competitor | [You] vs [Competitor], [Competitor] vs [You] |
| Competitor vs Competitor | [A] vs [B], [B] vs [A] | | Competitor vs Competitor | [A] vs [B], [B] vs [A] |
### Internal Linking ### Internal Linking
- Link between related competitor pages - Link between related competitor pages
- Link from feature pages to relevant comparisons - Link from feature pages to relevant comparisons
- Create hub page linking to all competitor content - Create hub page linking to all competitor content
### Schema Markup ### Schema Markup
Consider FAQ schema for common questions like "What is the best alternative to [Competitor]?" Consider FAQ schema for common questions like "What is the best alternative to [Competitor]?"
--- ---
@@ -227,12 +245,15 @@ Consider FAQ schema for common questions like "What is the best alternative to [
## Output Format ## Output Format
### Competitor Data File ### Competitor Data File
Complete competitor profile in YAML format for use across all comparison pages. Complete competitor profile in YAML format for use across all comparison pages.
### Page Content ### Page Content
For each page: URL, meta tags, full page copy organized by section, comparison tables, CTAs. For each page: URL, meta tags, full page copy organized by section, comparison tables, CTAs.
### Page Set Plan ### Page Set Plan
Recommended pages to create with priority order based on search volume. Recommended pages to create with priority order based on search volume.
--- ---
@@ -23,19 +23,19 @@ Per competitor, document:
```yaml ```yaml
name: Notion name: Notion
website: notion.so website: notion.so
tagline: "The all-in-one workspace" tagline: 'The all-in-one workspace'
founded: 2016 founded: 2016
headquarters: San Francisco headquarters: San Francisco
# Positioning # Positioning
primary_use_case: "docs + light databases" primary_use_case: 'docs + light databases'
target_audience: "teams wanting flexible workspace" target_audience: 'teams wanting flexible workspace'
market_position: "premium, feature-rich" market_position: 'premium, feature-rich'
# Pricing # Pricing
pricing_model: per-seat pricing_model: per-seat
free_tier: true free_tier: true
free_tier_limits: "limited blocks, 1 user" free_tier_limits: 'limited blocks, 1 user'
starter_price: $8/user/month starter_price: $8/user/month
business_price: $15/user/month business_price: $15/user/month
enterprise: custom enterprise: custom
@@ -81,17 +81,17 @@ not_ideal_for:
# Common complaints (from reviews) # Common complaints (from reviews)
common_complaints: common_complaints:
- "Gets slow with lots of content" - 'Gets slow with lots of content'
- "Hard to find things as workspace grows" - 'Hard to find things as workspace grows'
- "Mobile app is clunky" - 'Mobile app is clunky'
# Migration notes # Migration notes
migration_from: migration_from:
difficulty: medium difficulty: medium
data_export: "Markdown, CSV, HTML" data_export: 'Markdown, CSV, HTML'
what_transfers: "Pages, databases" what_transfers: 'Pages, databases'
what_doesnt: "Automations, integrations setup" what_doesnt: 'Automations, integrations setup'
time_estimate: "1-3 days for small team" time_estimate: '1-3 days for small team'
``` ```
--- ---
@@ -129,6 +129,7 @@ Each page pulls from centralized data:
- **[A] vs [B] page**: Pulls both competitor data + your data - **[A] vs [B] page**: Pulls both competitor data + your data
**Benefits**: **Benefits**:
- Update competitor pricing once, updates everywhere - Update competitor pricing once, updates everywhere
- Add new feature comparison once, appears on all pages - Add new feature comparison once, appears on all pages
- Consistent accuracy across pages - Consistent accuracy across pages
@@ -145,6 +146,7 @@ Each page pulls from centralized data:
**Purpose**: Lists all "[Competitor] Alternative" pages **Purpose**: Lists all "[Competitor] Alternative" pages
**Page structure**: **Page structure**:
1. Headline: "[Your Product] as an Alternative" 1. Headline: "[Your Product] as an Alternative"
2. Brief intro on why people switch to you 2. Brief intro on why people switch to you
3. List of all alternative pages with: 3. List of all alternative pages with:
@@ -155,6 +157,7 @@ Each page pulls from centralized data:
5. CTA 5. CTA
**Example**: **Example**:
```markdown ```markdown
## Explore [Your Product] as an Alternative ## Explore [Your Product] as an Alternative
@@ -174,6 +177,7 @@ Looking to switch? See how [Your Product] compares to the tools you're evaluatin
**Purpose**: Lists all "You vs [Competitor]" and "[A] vs [B]" pages **Purpose**: Lists all "You vs [Competitor]" and "[A] vs [B]" pages
**Page structure**: **Page structure**:
1. Headline: "Compare [Your Product]" 1. Headline: "Compare [Your Product]"
2. Section: "[Your Product] vs Competitors" — list of direct comparisons 2. Section: "[Your Product] vs Competitors" — list of direct comparisons
3. Section: "Head-to-Head Comparisons" — list of [A] vs [B] pages 3. Section: "Head-to-Head Comparisons" — list of [A] vs [B] pages
@@ -187,22 +191,26 @@ Looking to switch? See how [Your Product] compares to the tools you're evaluatin
**Keep them updated**: When you add a new comparison page, add it to the relevant index. **Keep them updated**: When you add a new comparison page, add it to the relevant index.
**Internal linking**: **Internal linking**:
- Link from index → individual pages - Link from index → individual pages
- Link from individual pages → back to index - Link from individual pages → back to index
- Cross-link between related comparisons - Cross-link between related comparisons
**SEO value**: **SEO value**:
- Index pages can rank for broad terms like "project management tool comparisons" - Index pages can rank for broad terms like "project management tool comparisons"
- Pass link equity to individual comparison pages - Pass link equity to individual comparison pages
- Help search engines discover all comparison content - Help search engines discover all comparison content
**Sorting options**: **Sorting options**:
- By popularity (search volume) - By popularity (search volume)
- Alphabetically - Alphabetically
- By category/use case - By category/use case
- By date added (show freshness) - By date added (show freshness)
**Include on index pages**: **Include on index pages**:
- Last updated date for credibility - Last updated date for credibility
- Number of pages/comparisons available - Number of pages/comparisons available
- Quick filters if you have many comparisons - Quick filters if you have many comparisons
@@ -243,6 +251,7 @@ Footer
``` ```
**Guidelines**: **Guidelines**:
- Include up to 8 links per column (top competitors by search volume) - Include up to 8 links per column (top competitors by search volume)
- Add "View all" link to the full index page - Add "View all" link to the full index page
- Only create columns for formats you've actually built pages for - Only create columns for formats you've actually built pages for
@@ -42,10 +42,12 @@ Go beyond checkmarks:
### [Feature Category] ### [Feature Category]
**[Competitor]**: [2-3 sentence description of how they handle this] **[Competitor]**: [2-3 sentence description of how they handle this]
- Strengths: [specific] - Strengths: [specific]
- Limitations: [specific] - Limitations: [specific]
**[Your product]**: [2-3 sentence description] **[Your product]**: [2-3 sentence description]
- Strengths: [specific] - Strengths: [specific]
- Limitations: [specific] - Limitations: [specific]
@@ -59,12 +61,12 @@ Go beyond checkmarks:
```markdown ```markdown
## Pricing ## Pricing
| | [Competitor] | [Your Product] | | | [Competitor] | [Your Product] |
|---|---|---| | -------------- | ------------ | -------------- |
| Free tier | [Details] | [Details] | | Free tier | [Details] | [Details] |
| Starting price | $X/user/mo | $X/user/mo | | Starting price | $X/user/mo | $X/user/mo |
| Business tier | $X/user/mo | $X/user/mo | | Business tier | $X/user/mo | $X/user/mo |
| Enterprise | Custom | Custom | | Enterprise | Custom | Custom |
**What's included**: [Competitor]'s $X plan includes [features], while **What's included**: [Competitor]'s $X plan includes [features], while
[Your product]'s $X plan includes [features]. [Your product]'s $X plan includes [features].
@@ -84,13 +86,13 @@ $X/year while [Your product] costs $Y/year, with [key differences in what you ge
```markdown ```markdown
## Service & Support ## Service & Support
| | [Competitor] | [Your Product] | | | [Competitor] | [Your Product] |
|---|---|---| | ---------------- | -------------------- | -------------------- |
| Documentation | [Quality assessment] | [Quality assessment] | | Documentation | [Quality assessment] | [Quality assessment] |
| Response time | [SLA if known] | [Your SLA] | | Response time | [SLA if known] | [Your SLA] |
| Support channels | [List] | [List] | | Support channels | [List] | [List] |
| Onboarding | [What they offer] | [What you offer] | | Onboarding | [What they offer] | [What you offer] |
| CSM included | [At what tier] | [At what tier] | | CSM included | [At what tier] | [At what tier] |
**Support quality**: Based on [G2/Capterra reviews, your research], **Support quality**: Based on [G2/Capterra reviews, your research],
[Competitor] support is described as [assessment]. Common feedback includes [Competitor] support is described as [assessment]. Common feedback includes
@@ -108,6 +110,7 @@ response time, dedicated CSM, implementation help].
## Who Should Choose [Competitor] ## Who Should Choose [Competitor]
[Competitor] is the right choice if: [Competitor] is the right choice if:
- [Specific use case or need] - [Specific use case or need]
- [Team type or size] - [Team type or size]
- [Workflow or requirement] - [Workflow or requirement]
@@ -118,6 +121,7 @@ response time, dedicated CSM, implementation help].
## Who Should Choose [Your Product] ## Who Should Choose [Your Product]
[Your product] is built for teams who: [Your product] is built for teams who:
- [Specific use case or need] - [Specific use case or need]
- [Team type or size] - [Team type or size]
- [Workflow or requirement] - [Workflow or requirement]
@@ -134,16 +138,19 @@ response time, dedicated CSM, implementation help].
## Switching from [Competitor] ## Switching from [Competitor]
### What transfers ### What transfers
- [Data type]: [How easily, any caveats] - [Data type]: [How easily, any caveats]
- [Data type]: [How easily, any caveats] - [Data type]: [How easily, any caveats]
### What needs reconfiguration ### What needs reconfiguration
- [Thing]: [Why and effort level] - [Thing]: [Why and effort level]
- [Thing]: [Why and effort level] - [Thing]: [Why and effort level]
### Migration support ### Migration support
We offer [migration support details]: We offer [migration support details]:
- [Free data import tool / white-glove migration] - [Free data import tool / white-glove migration]
- [Documentation / migration guide] - [Documentation / migration guide]
- [Timeline expectation] - [Timeline expectation]
@@ -173,6 +180,7 @@ Focus on switchers:
> — [Name], [Role] at [Company] > — [Name], [Role] at [Company]
### Results after switching ### Results after switching
- [Company] saw [specific result] - [Company] saw [specific result]
- [Company] reduced [metric] by [amount] - [Company] reduced [metric] by [amount]
``` ```
@@ -198,6 +206,7 @@ Do this:
### Organize by Category ### Organize by Category
Group features into meaningful categories: Group features into meaningful categories:
- Core functionality - Core functionality
- Collaboration - Collaboration
- Integrations - Integrations
@@ -206,7 +215,7 @@ Group features into meaningful categories:
### Include Ratings Where Useful ### Include Ratings Where Useful
| Category | You | Competitor | Notes | | Category | You | Competitor | Notes |
|----------|-----|-----------|-------| | ------------- | ---------- | ---------- | ------------ |
| Ease of use | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | [Brief note] | | Ease of use | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | [Brief note] |
| Feature depth | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | [Brief note] | | Feature depth | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | [Brief note] |
@@ -16,23 +16,27 @@ If `.mosaic/product-marketing-context.md` exists, read it before asking question
Gather this context (ask if not provided): Gather this context (ask if not provided):
### 1. Business Context ### 1. Business Context
- What does the company do? - What does the company do?
- Who is the ideal customer? - Who is the ideal customer?
- What's the primary goal for content? (traffic, leads, brand awareness, thought leadership) - What's the primary goal for content? (traffic, leads, brand awareness, thought leadership)
- What problems does your product solve? - What problems does your product solve?
### 2. Customer Research ### 2. Customer Research
- What questions do customers ask before buying? - What questions do customers ask before buying?
- What objections come up in sales calls? - What objections come up in sales calls?
- What topics appear repeatedly in support tickets? - What topics appear repeatedly in support tickets?
- What language do customers use to describe their problems? - What language do customers use to describe their problems?
### 3. Current State ### 3. Current State
- Do you have existing content? What's working? - Do you have existing content? What's working?
- What resources do you have? (writers, budget, time) - What resources do you have? (writers, budget, time)
- What content formats can you produce? (written, video, audio) - What content formats can you produce? (written, video, audio)
### 4. Competitive Landscape ### 4. Competitive Landscape
- Who are your main competitors? - Who are your main competitors?
- What content gaps exist in your market? - What content gaps exist in your market?
@@ -74,24 +78,28 @@ Every piece of content must be searchable, shareable, or both. Prioritize in tha
**Use-Case Content** **Use-Case Content**
Formula: [persona] + [use-case]. Targets long-tail keywords. Formula: [persona] + [use-case]. Targets long-tail keywords.
- "Project management for designers" - "Project management for designers"
- "Task tracking for developers" - "Task tracking for developers"
- "Client collaboration for freelancers" - "Client collaboration for freelancers"
**Hub and Spoke** **Hub and Spoke**
Hub = comprehensive overview. Spokes = related subtopics. Hub = comprehensive overview. Spokes = related subtopics.
``` ```
/topic (hub) /topic (hub)
├── /topic/subtopic-1 (spoke) ├── /topic/subtopic-1 (spoke)
├── /topic/subtopic-2 (spoke) ├── /topic/subtopic-2 (spoke)
└── /topic/subtopic-3 (spoke) └── /topic/subtopic-3 (spoke)
``` ```
Create hub first, then build spokes. Interlink strategically. Create hub first, then build spokes. Interlink strategically.
**Note:** Most content works fine under `/blog`. Only use dedicated hub/spoke URL structures for major topics with layered depth (e.g., Atlassian's `/agile` guide). For typical blog posts, `/blog/post-title` is sufficient. **Note:** Most content works fine under `/blog`. Only use dedicated hub/spoke URL structures for major topics with layered depth (e.g., Atlassian's `/agile` guide). For typical blog posts, `/blog/post-title` is sufficient.
**Template Libraries** **Template Libraries**
High-intent keywords + product adoption. High-intent keywords + product adoption.
- Target searches like "marketing plan template" - Target searches like "marketing plan template"
- Provide immediate standalone value - Provide immediate standalone value
- Show how product enhances the template - Show how product enhances the template
@@ -99,11 +107,13 @@ High-intent keywords + product adoption.
### Shareable Content Types ### Shareable Content Types
**Thought Leadership** **Thought Leadership**
- Articulate concepts everyone feels but hasn't named - Articulate concepts everyone feels but hasn't named
- Challenge conventional wisdom with evidence - Challenge conventional wisdom with evidence
- Share vulnerable, honest experiences - Share vulnerable, honest experiences
**Data-Driven Content** **Data-Driven Content**
- Product data analysis (anonymized insights) - Product data analysis (anonymized insights)
- Public data analysis (uncover patterns) - Public data analysis (uncover patterns)
- Original research (run experiments, share results) - Original research (run experiments, share results)
@@ -155,6 +165,7 @@ Pillar Topic (Hub)
### Pillar Criteria ### Pillar Criteria
Good pillars should: Good pillars should:
- Align with your product/service - Align with your product/service
- Match what your audience cares about - Match what your audience cares about
- Have search volume and/or social interest - Have search volume and/or social interest
@@ -167,33 +178,41 @@ Good pillars should:
Map topics to the buyer's journey using proven keyword modifiers: Map topics to the buyer's journey using proven keyword modifiers:
### Awareness Stage ### Awareness Stage
Modifiers: "what is," "how to," "guide to," "introduction to" Modifiers: "what is," "how to," "guide to," "introduction to"
Example: If customers ask about project management basics: Example: If customers ask about project management basics:
- "What is Agile Project Management" - "What is Agile Project Management"
- "Guide to Sprint Planning" - "Guide to Sprint Planning"
- "How to Run a Standup Meeting" - "How to Run a Standup Meeting"
### Consideration Stage ### Consideration Stage
Modifiers: "best," "top," "vs," "alternatives," "comparison" Modifiers: "best," "top," "vs," "alternatives," "comparison"
Example: If customers evaluate multiple tools: Example: If customers evaluate multiple tools:
- "Best Project Management Tools for Remote Teams" - "Best Project Management Tools for Remote Teams"
- "Asana vs Trello vs Monday" - "Asana vs Trello vs Monday"
- "Basecamp Alternatives" - "Basecamp Alternatives"
### Decision Stage ### Decision Stage
Modifiers: "pricing," "reviews," "demo," "trial," "buy" Modifiers: "pricing," "reviews," "demo," "trial," "buy"
Example: If pricing comes up in sales calls: Example: If pricing comes up in sales calls:
- "Project Management Tool Pricing Comparison" - "Project Management Tool Pricing Comparison"
- "How to Choose the Right Plan" - "How to Choose the Right Plan"
- "[Product] Reviews" - "[Product] Reviews"
### Implementation Stage ### Implementation Stage
Modifiers: "templates," "examples," "tutorial," "how to use," "setup" Modifiers: "templates," "examples," "tutorial," "how to use," "setup"
Example: If support tickets show implementation struggles: Example: If support tickets show implementation struggles:
- "Project Template Library" - "Project Template Library"
- "Step-by-Step Setup Tutorial" - "Step-by-Step Setup Tutorial"
- "How to Use [Feature]" - "How to Use [Feature]"
@@ -205,6 +224,7 @@ Example: If support tickets show implementation struggles:
### 1. Keyword Data ### 1. Keyword Data
If user provides keyword exports (Ahrefs, SEMrush, GSC), analyze for: If user provides keyword exports (Ahrefs, SEMrush, GSC), analyze for:
- Topic clusters (group related keywords) - Topic clusters (group related keywords)
- Buyer stage (awareness/consideration/decision/implementation) - Buyer stage (awareness/consideration/decision/implementation)
- Search intent (informational, commercial, transactional) - Search intent (informational, commercial, transactional)
@@ -217,6 +237,7 @@ Output as prioritized table:
### 2. Call Transcripts ### 2. Call Transcripts
If user provides sales or customer call transcripts, extract: If user provides sales or customer call transcripts, extract:
- Questions asked → FAQ content or blog posts - Questions asked → FAQ content or blog posts
- Pain points → problems in their own words - Pain points → problems in their own words
- Objections → content to address proactively - Objections → content to address proactively
@@ -228,6 +249,7 @@ Output content ideas with supporting quotes.
### 3. Survey Responses ### 3. Survey Responses
If user provides survey data, mine for: If user provides survey data, mine for:
- Open-ended responses (topics and language) - Open-ended responses (topics and language)
- Common themes (30%+ mention = high priority) - Common themes (30%+ mention = high priority)
- Resource requests (what they wish existed) - Resource requests (what they wish existed)
@@ -238,11 +260,13 @@ If user provides survey data, mine for:
Use web search to find content ideas: Use web search to find content ideas:
**Reddit:** `site:reddit.com [topic]` **Reddit:** `site:reddit.com [topic]`
- Top posts in relevant subreddits - Top posts in relevant subreddits
- Questions and frustrations in comments - Questions and frustrations in comments
- Upvoted answers (validates what resonates) - Upvoted answers (validates what resonates)
**Quora:** `site:quora.com [topic]` **Quora:** `site:quora.com [topic]`
- Most-followed questions - Most-followed questions
- Highly upvoted answers - Highly upvoted answers
@@ -257,6 +281,7 @@ Use web search to analyze competitor content:
**Find their content:** `site:competitor.com/blog` **Find their content:** `site:competitor.com/blog`
**Analyze:** **Analyze:**
- Top-performing posts (comments, shares) - Top-performing posts (comments, shares)
- Topics covered repeatedly - Topics covered repeatedly
- Gaps they haven't covered - Gaps they haven't covered
@@ -264,6 +289,7 @@ Use web search to analyze competitor content:
- Content structure (pillars, categories, formats) - Content structure (pillars, categories, formats)
**Identify opportunities:** **Identify opportunities:**
- Topics you can cover better - Topics you can cover better
- Angles they're missing - Angles they're missing
- Outdated content to improve on - Outdated content to improve on
@@ -271,6 +297,7 @@ Use web search to analyze competitor content:
### 6. Sales and Support Input ### 6. Sales and Support Input
Extract from customer-facing teams: Extract from customer-facing teams:
- Common objections - Common objections
- Repeated questions - Repeated questions
- Support ticket patterns - Support ticket patterns
@@ -284,34 +311,38 @@ Extract from customer-facing teams:
Score each idea on four factors: Score each idea on four factors:
### 1. Customer Impact (40%) ### 1. Customer Impact (40%)
- How frequently did this topic come up in research? - How frequently did this topic come up in research?
- What percentage of customers face this challenge? - What percentage of customers face this challenge?
- How emotionally charged was this pain point? - How emotionally charged was this pain point?
- What's the potential LTV of customers with this need? - What's the potential LTV of customers with this need?
### 2. Content-Market Fit (30%) ### 2. Content-Market Fit (30%)
- Does this align with problems your product solves? - Does this align with problems your product solves?
- Can you offer unique insights from customer research? - Can you offer unique insights from customer research?
- Do you have customer stories to support this? - Do you have customer stories to support this?
- Will this naturally lead to product interest? - Will this naturally lead to product interest?
### 3. Search Potential (20%) ### 3. Search Potential (20%)
- What's the monthly search volume? - What's the monthly search volume?
- How competitive is this topic? - How competitive is this topic?
- Are there related long-tail opportunities? - Are there related long-tail opportunities?
- Is search interest growing or declining? - Is search interest growing or declining?
### 4. Resource Requirements (10%) ### 4. Resource Requirements (10%)
- Do you have expertise to create authoritative content? - Do you have expertise to create authoritative content?
- What additional research is needed? - What additional research is needed?
- What assets (graphics, data, examples) will you need? - What assets (graphics, data, examples) will you need?
### Scoring Template ### Scoring Template
| Idea | Customer Impact (40%) | Content-Market Fit (30%) | Search Potential (20%) | Resources (10%) | Total | | Idea | Customer Impact (40%) | Content-Market Fit (30%) | Search Potential (20%) | Resources (10%) | Total |
|------|----------------------|-------------------------|----------------------|-----------------|-------| | ------- | --------------------- | ------------------------ | ---------------------- | --------------- | ----- |
| Topic A | 8 | 9 | 7 | 6 | 8.0 | | Topic A | 8 | 9 | 7 | 6 | 8.0 |
| Topic B | 6 | 7 | 9 | 8 | 7.1 | | Topic B | 6 | 7 | 9 | 8 | 7.1 |
--- ---
@@ -320,12 +351,15 @@ Score each idea on four factors:
When creating a content strategy, provide: When creating a content strategy, provide:
### 1. Content Pillars ### 1. Content Pillars
- 3-5 pillars with rationale - 3-5 pillars with rationale
- Subtopic clusters for each pillar - Subtopic clusters for each pillar
- How pillars connect to product - How pillars connect to product
### 2. Priority Topics ### 2. Priority Topics
For each recommended piece: For each recommended piece:
- Topic/title - Topic/title
- Searchable, shareable, or both - Searchable, shareable, or both
- Content type (use-case, hub/spoke, thought leadership, etc.) - Content type (use-case, hub/spoke, thought leadership, etc.)
@@ -333,6 +367,7 @@ For each recommended piece:
- Why this topic (customer research backing) - Why this topic (customer research backing)
### 3. Topic Cluster Map ### 3. Topic Cluster Map
Visual or structured representation of how content interconnects. Visual or structured representation of how content interconnects.
--- ---
@@ -16,6 +16,7 @@ If `.mosaic/product-marketing-context.md` exists, read it before editing. Use br
Good copy editing isn't about rewriting—it's about enhancing. Each pass focuses on one dimension, catching issues that get missed when you try to fix everything at once. Good copy editing isn't about rewriting—it's about enhancing. Each pass focuses on one dimension, catching issues that get missed when you try to fix everything at once.
**Key principles:** **Key principles:**
- Don't change the core message; focus on enhancing it - Don't change the core message; focus on enhancing it
- Multiple focused passes beat one unfocused review - Multiple focused passes beat one unfocused review
- Each edit should have a clear reason - Each edit should have a clear reason
@@ -32,6 +33,7 @@ Edit copy through seven sequential passes, each focusing on one dimension. After
**Focus:** Can the reader understand what you're saying? **Focus:** Can the reader understand what you're saying?
**What to check:** **What to check:**
- Confusing sentence structures - Confusing sentence structures
- Unclear pronoun references - Unclear pronoun references
- Jargon or insider language - Jargon or insider language
@@ -39,12 +41,14 @@ Edit copy through seven sequential passes, each focusing on one dimension. After
- Missing context - Missing context
**Common clarity killers:** **Common clarity killers:**
- Sentences trying to say too much - Sentences trying to say too much
- Abstract language instead of concrete - Abstract language instead of concrete
- Assuming reader knowledge they don't have - Assuming reader knowledge they don't have
- Burying the point in qualifications - Burying the point in qualifications
**Process:** **Process:**
1. Read through quickly, highlighting unclear parts 1. Read through quickly, highlighting unclear parts
2. Don't correct yet—just note problem areas 2. Don't correct yet—just note problem areas
3. After marking issues, recommend specific edits 3. After marking issues, recommend specific edits
@@ -59,18 +63,21 @@ Edit copy through seven sequential passes, each focusing on one dimension. After
**Focus:** Is the copy consistent in how it sounds? **Focus:** Is the copy consistent in how it sounds?
**What to check:** **What to check:**
- Shifts between formal and casual - Shifts between formal and casual
- Inconsistent brand personality - Inconsistent brand personality
- Mood changes that feel jarring - Mood changes that feel jarring
- Word choices that don't match the brand - Word choices that don't match the brand
**Common voice issues:** **Common voice issues:**
- Starting casual, becoming corporate - Starting casual, becoming corporate
- Mixing "we" and "the company" references - Mixing "we" and "the company" references
- Humor in some places, serious in others (unintentionally) - Humor in some places, serious in others (unintentionally)
- Technical language appearing randomly - Technical language appearing randomly
**Process:** **Process:**
1. Read aloud to hear inconsistencies 1. Read aloud to hear inconsistencies
2. Mark where tone shifts unexpectedly 2. Mark where tone shifts unexpectedly
3. Recommend edits that smooth transitions 3. Recommend edits that smooth transitions
@@ -85,6 +92,7 @@ Edit copy through seven sequential passes, each focusing on one dimension. After
**Focus:** Does every claim answer "why should I care?" **Focus:** Does every claim answer "why should I care?"
**What to check:** **What to check:**
- Features without benefits - Features without benefits
- Claims without consequences - Claims without consequences
- Statements that don't connect to reader's life - Statements that don't connect to reader's life
@@ -94,16 +102,18 @@ Edit copy through seven sequential passes, each focusing on one dimension. After
For every statement, ask "Okay, so what?" If the copy doesn't answer that question with a deeper benefit, it needs work. For every statement, ask "Okay, so what?" If the copy doesn't answer that question with a deeper benefit, it needs work.
❌ "Our platform uses AI-powered analytics" ❌ "Our platform uses AI-powered analytics"
*So what?* _So what?_
✅ "Our AI-powered analytics surface insights you'd miss manually—so you can make better decisions in half the time" ✅ "Our AI-powered analytics surface insights you'd miss manually—so you can make better decisions in half the time"
**Common So What failures:** **Common So What failures:**
- Feature lists without benefit connections - Feature lists without benefit connections
- Impressive-sounding claims that don't land - Impressive-sounding claims that don't land
- Technical capabilities without outcomes - Technical capabilities without outcomes
- Company achievements that don't help the reader - Company achievements that don't help the reader
**Process:** **Process:**
1. Read each claim and literally ask "so what?" 1. Read each claim and literally ask "so what?"
2. Highlight claims missing the answer 2. Highlight claims missing the answer
3. Add the benefit bridge or deeper meaning 3. Add the benefit bridge or deeper meaning
@@ -118,12 +128,14 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
**Focus:** Is every claim supported with evidence? **Focus:** Is every claim supported with evidence?
**What to check:** **What to check:**
- Unsubstantiated claims - Unsubstantiated claims
- Missing social proof - Missing social proof
- Assertions without backup - Assertions without backup
- "Best" or "leading" without evidence - "Best" or "leading" without evidence
**Types of proof to look for:** **Types of proof to look for:**
- Testimonials with names and specifics - Testimonials with names and specifics
- Case study references - Case study references
- Statistics and data - Statistics and data
@@ -133,12 +145,14 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
- Review scores - Review scores
**Common proof gaps:** **Common proof gaps:**
- "Trusted by thousands" (which thousands?) - "Trusted by thousands" (which thousands?)
- "Industry-leading" (according to whom?) - "Industry-leading" (according to whom?)
- "Customers love us" (show them saying it) - "Customers love us" (show them saying it)
- Results claims without specifics - Results claims without specifics
**Process:** **Process:**
1. Identify every claim that needs proof 1. Identify every claim that needs proof
2. Check if proof exists nearby 2. Check if proof exists nearby
3. Flag unsupported assertions 3. Flag unsupported assertions
@@ -153,6 +167,7 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
**Focus:** Is the copy concrete enough to be compelling? **Focus:** Is the copy concrete enough to be compelling?
**What to check:** **What to check:**
- Vague language ("improve," "enhance," "optimize") - Vague language ("improve," "enhance," "optimize")
- Generic statements that could apply to anyone - Generic statements that could apply to anyone
- Round numbers that feel made up - Round numbers that feel made up
@@ -160,21 +175,23 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
**Specificity upgrades:** **Specificity upgrades:**
| Vague | Specific | | Vague | Specific |
|-------|----------| | --------------------- | ------------------------------- |
| Save time | Save 4 hours every week | | Save time | Save 4 hours every week |
| Many customers | 2,847 teams | | Many customers | 2,847 teams |
| Fast results | Results in 14 days | | Fast results | Results in 14 days |
| Improve your workflow | Cut your reporting time in half | | Improve your workflow | Cut your reporting time in half |
| Great support | Response within 2 hours | | Great support | Response within 2 hours |
**Common specificity issues:** **Common specificity issues:**
- Adjectives doing the work nouns should do - Adjectives doing the work nouns should do
- Benefits without quantification - Benefits without quantification
- Outcomes without timeframes - Outcomes without timeframes
- Claims without concrete examples - Claims without concrete examples
**Process:** **Process:**
1. Highlight vague words and phrases 1. Highlight vague words and phrases
2. Ask "Can this be more specific?" 2. Ask "Can this be more specific?"
3. Add numbers, timeframes, or examples 3. Add numbers, timeframes, or examples
@@ -189,12 +206,14 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
**Focus:** Does the copy make the reader feel something? **Focus:** Does the copy make the reader feel something?
**What to check:** **What to check:**
- Flat, informational language - Flat, informational language
- Missing emotional triggers - Missing emotional triggers
- Pain points mentioned but not felt - Pain points mentioned but not felt
- Aspirations stated but not evoked - Aspirations stated but not evoked
**Emotional dimensions to consider:** **Emotional dimensions to consider:**
- Pain of the current state - Pain of the current state
- Frustration with alternatives - Frustration with alternatives
- Fear of missing out - Fear of missing out
@@ -203,6 +222,7 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
- Relief from solving the problem - Relief from solving the problem
**Techniques for heightening emotion:** **Techniques for heightening emotion:**
- Paint the "before" state vividly - Paint the "before" state vividly
- Use sensory language - Use sensory language
- Tell micro-stories - Tell micro-stories
@@ -210,6 +230,7 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
- Ask questions that prompt reflection - Ask questions that prompt reflection
**Process:** **Process:**
1. Read for emotional impact—does it move you? 1. Read for emotional impact—does it move you?
2. Identify flat sections that should resonate 2. Identify flat sections that should resonate
3. Add emotional texture while staying authentic 3. Add emotional texture while staying authentic
@@ -224,6 +245,7 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
**Focus:** Have we removed every barrier to action? **Focus:** Have we removed every barrier to action?
**What to check:** **What to check:**
- Friction near CTAs - Friction near CTAs
- Unanswered objections - Unanswered objections
- Missing trust signals - Missing trust signals
@@ -231,6 +253,7 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
- Hidden costs or surprises - Hidden costs or surprises
**Risk reducers to look for:** **Risk reducers to look for:**
- Money-back guarantees - Money-back guarantees
- Free trials - Free trials
- "No credit card required" - "No credit card required"
@@ -240,12 +263,14 @@ For every statement, ask "Okay, so what?" If the copy doesn't answer that questi
- Privacy assurances - Privacy assurances
**Common risk issues:** **Common risk issues:**
- CTA asks for commitment without earning trust - CTA asks for commitment without earning trust
- Objections raised but not addressed - Objections raised but not addressed
- Fine print that creates doubt - Fine print that creates doubt
- Vague "Contact us" instead of clear next step - Vague "Contact us" instead of clear next step
**Process:** **Process:**
1. Focus on sections near CTAs 1. Focus on sections near CTAs
2. List every reason someone might hesitate 2. List every reason someone might hesitate
3. Check if the copy addresses each concern 3. Check if the copy addresses each concern
@@ -262,6 +287,7 @@ Use these for faster reviews when a full seven-sweep process isn't needed.
### Word-Level Checks ### Word-Level Checks
**Cut these words:** **Cut these words:**
- Very, really, extremely, incredibly (weak intensifiers) - Very, really, extremely, incredibly (weak intensifiers)
- Just, actually, basically (filler) - Just, actually, basically (filler)
- In order to (use "to") - In order to (use "to")
@@ -270,18 +296,19 @@ Use these for faster reviews when a full seven-sweep process isn't needed.
**Replace these:** **Replace these:**
| Weak | Strong | | Weak | Strong |
|------|--------| | ------------ | ---------- |
| Utilize | Use | | Utilize | Use |
| Implement | Set up | | Implement | Set up |
| Leverage | Use | | Leverage | Use |
| Facilitate | Help | | Facilitate | Help |
| Innovative | New | | Innovative | New |
| Robust | Strong | | Robust | Strong |
| Seamless | Smooth | | Seamless | Smooth |
| Cutting-edge | New/Modern | | Cutting-edge | New/Modern |
**Watch for:** **Watch for:**
- Adverbs (usually unnecessary) - Adverbs (usually unnecessary)
- Passive voice (switch to active) - Passive voice (switch to active)
- Nominalizations (verb → noun: "make a decision" → "decide") - Nominalizations (verb → noun: "make a decision" → "decide")
@@ -307,54 +334,63 @@ Use these for faster reviews when a full seven-sweep process isn't needed.
## Copy Editing Checklist ## Copy Editing Checklist
### Before You Start ### Before You Start
- [ ] Understand the goal of this copy - [ ] Understand the goal of this copy
- [ ] Know the target audience - [ ] Know the target audience
- [ ] Identify the desired action - [ ] Identify the desired action
- [ ] Read through once without editing - [ ] Read through once without editing
### Clarity (Sweep 1) ### Clarity (Sweep 1)
- [ ] Every sentence is immediately understandable - [ ] Every sentence is immediately understandable
- [ ] No jargon without explanation - [ ] No jargon without explanation
- [ ] Pronouns have clear references - [ ] Pronouns have clear references
- [ ] No sentences trying to do too much - [ ] No sentences trying to do too much
### Voice & Tone (Sweep 2) ### Voice & Tone (Sweep 2)
- [ ] Consistent formality level throughout - [ ] Consistent formality level throughout
- [ ] Brand personality maintained - [ ] Brand personality maintained
- [ ] No jarring shifts in mood - [ ] No jarring shifts in mood
- [ ] Reads well aloud - [ ] Reads well aloud
### So What (Sweep 3) ### So What (Sweep 3)
- [ ] Every feature connects to a benefit - [ ] Every feature connects to a benefit
- [ ] Claims answer "why should I care?" - [ ] Claims answer "why should I care?"
- [ ] Benefits connect to real desires - [ ] Benefits connect to real desires
- [ ] No impressive-but-empty statements - [ ] No impressive-but-empty statements
### Prove It (Sweep 4) ### Prove It (Sweep 4)
- [ ] Claims are substantiated - [ ] Claims are substantiated
- [ ] Social proof is specific and attributed - [ ] Social proof is specific and attributed
- [ ] Numbers and stats have sources - [ ] Numbers and stats have sources
- [ ] No unearned superlatives - [ ] No unearned superlatives
### Specificity (Sweep 5) ### Specificity (Sweep 5)
- [ ] Vague words replaced with concrete ones - [ ] Vague words replaced with concrete ones
- [ ] Numbers and timeframes included - [ ] Numbers and timeframes included
- [ ] Generic statements made specific - [ ] Generic statements made specific
- [ ] Filler content removed - [ ] Filler content removed
### Heightened Emotion (Sweep 6) ### Heightened Emotion (Sweep 6)
- [ ] Copy evokes feeling, not just information - [ ] Copy evokes feeling, not just information
- [ ] Pain points feel real - [ ] Pain points feel real
- [ ] Aspirations feel achievable - [ ] Aspirations feel achievable
- [ ] Emotion serves the message authentically - [ ] Emotion serves the message authentically
### Zero Risk (Sweep 7) ### Zero Risk (Sweep 7)
- [ ] Objections addressed near CTA - [ ] Objections addressed near CTA
- [ ] Trust signals present - [ ] Trust signals present
- [ ] Next steps are crystal clear - [ ] Next steps are crystal clear
- [ ] Risk reversals stated (guarantee, trial, etc.) - [ ] Risk reversals stated (guarantee, trial, etc.)
### Final Checks ### Final Checks
- [ ] No typos or grammatical errors - [ ] No typos or grammatical errors
- [ ] Consistent formatting - [ ] Consistent formatting
- [ ] Links work (if applicable) - [ ] Links work (if applicable)
@@ -365,34 +401,42 @@ Use these for faster reviews when a full seven-sweep process isn't needed.
## Common Copy Problems & Fixes ## Common Copy Problems & Fixes
### Problem: Wall of Features ### Problem: Wall of Features
**Symptom:** List of what the product does without why it matters **Symptom:** List of what the product does without why it matters
**Fix:** Add "which means..." after each feature to bridge to benefits **Fix:** Add "which means..." after each feature to bridge to benefits
### Problem: Corporate Speak ### Problem: Corporate Speak
**Symptom:** "Leverage synergies to optimize outcomes" **Symptom:** "Leverage synergies to optimize outcomes"
**Fix:** Ask "How would a human say this?" and use those words **Fix:** Ask "How would a human say this?" and use those words
### Problem: Weak Opening ### Problem: Weak Opening
**Symptom:** Starting with company history or vague statements **Symptom:** Starting with company history or vague statements
**Fix:** Lead with the reader's problem or desired outcome **Fix:** Lead with the reader's problem or desired outcome
### Problem: Buried CTA ### Problem: Buried CTA
**Symptom:** The ask comes after too much buildup, or isn't clear **Symptom:** The ask comes after too much buildup, or isn't clear
**Fix:** Make the CTA obvious, early, and repeated **Fix:** Make the CTA obvious, early, and repeated
### Problem: No Proof ### Problem: No Proof
**Symptom:** "Customers love us" with no evidence **Symptom:** "Customers love us" with no evidence
**Fix:** Add specific testimonials, numbers, or case references **Fix:** Add specific testimonials, numbers, or case references
### Problem: Generic Claims ### Problem: Generic Claims
**Symptom:** "We help businesses grow" **Symptom:** "We help businesses grow"
**Fix:** Specify who, how, and by how much **Fix:** Specify who, how, and by how much
### Problem: Mixed Audiences ### Problem: Mixed Audiences
**Symptom:** Copy tries to speak to everyone, resonates with no one **Symptom:** Copy tries to speak to everyone, resonates with no one
**Fix:** Pick one audience and write directly to them **Fix:** Pick one audience and write directly to them
### Problem: Feature Overload ### Problem: Feature Overload
**Symptom:** Listing every capability, overwhelming the reader **Symptom:** Listing every capability, overwhelming the reader
**Fix:** Focus on 3-5 key benefits that matter most to the audience **Fix:** Focus on 3-5 key benefits that matter most to the audience
@@ -438,9 +482,9 @@ This iterative process ensures each edit doesn't create new problems while respe
## When to Use Each Skill ## When to Use Each Skill
| Task | Skill to Use | | Task | Skill to Use |
|------|--------------| | ------------------------------------- | ------------------------- |
| Writing new page copy from scratch | copywriting | | Writing new page copy from scratch | copywriting |
| Reviewing and improving existing copy | copy-editing (this skill) | | Reviewing and improving existing copy | copy-editing (this skill) |
| Editing copy you just wrote | copy-editing (this skill) | | Editing copy you just wrote | copy-editing (this skill) |
| Structural or strategic page changes | page-cro | | Structural or strategic page changes | page-cro |
@@ -8,344 +8,344 @@ Source: Plain English Campaign A-Z of Alternative Words (2001), Australian Gover
## A ## A
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------------- | ----------------------- |
| (an) absence of | no, none | | (an) absence of | no, none |
| abundance | enough, plenty, many | | abundance | enough, plenty, many |
| accede to | allow, agree to | | accede to | allow, agree to |
| accelerate | speed up | | accelerate | speed up |
| accommodate | meet, hold, house | | accommodate | meet, hold, house |
| accomplish | do, finish, complete | | accomplish | do, finish, complete |
| accordingly | so, therefore | | accordingly | so, therefore |
| acknowledge | thank you for, confirm | | acknowledge | thank you for, confirm |
| acquire | get, buy, obtain | | acquire | get, buy, obtain |
| additional | extra, more | | additional | extra, more |
| adjacent | next to | | adjacent | next to |
| advantageous | useful, helpful | | advantageous | useful, helpful |
| advise | tell, say, inform | | advise | tell, say, inform |
| aforesaid | this, earlier | | aforesaid | this, earlier |
| aggregate | total | | aggregate | total |
| alleviate | ease, reduce | | alleviate | ease, reduce |
| allocate | give, share, assign | | allocate | give, share, assign |
| alternative | other, choice | | alternative | other, choice |
| ameliorate | improve | | ameliorate | improve |
| anticipate | expect | | anticipate | expect |
| apparent | clear, obvious | | apparent | clear, obvious |
| appreciable | large, noticeable | | appreciable | large, noticeable |
| appropriate | proper, right, suitable | | appropriate | proper, right, suitable |
| approximately | about, roughly | | approximately | about, roughly |
| ascertain | find out | | ascertain | find out |
| assistance | help | | assistance | help |
| at the present time | now | | at the present time | now |
| attempt | try | | attempt | try |
| authorise | allow, let | | authorise | allow, let |
--- ---
## B ## B
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ----------- | ----------------- |
| belated | late | | belated | late |
| beneficial | helpful, useful | | beneficial | helpful, useful |
| bestow | give | | bestow | give |
| by means of | by | | by means of | by |
--- ---
## C ## C
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------------ | ------------------ |
| calculate | work out | | calculate | work out |
| cease | stop, end | | cease | stop, end |
| circumvent | avoid, get around | | circumvent | avoid, get around |
| clarification | explanation | | clarification | explanation |
| commence | start, begin | | commence | start, begin |
| communicate | tell, talk, write | | communicate | tell, talk, write |
| competent | able | | competent | able |
| compile | collect, make | | compile | collect, make |
| complete | fill in, finish | | complete | fill in, finish |
| component | part | | component | part |
| comprise | include, make up | | comprise | include, make up |
| (it is) compulsory | (you) must | | (it is) compulsory | (you) must |
| conceal | hide | | conceal | hide |
| concerning | about | | concerning | about |
| consequently | so | | consequently | so |
| considerable | large, great, much | | considerable | large, great, much |
| constitute | make up, form | | constitute | make up, form |
| consult | ask, talk to | | consult | ask, talk to |
| consumption | use | | consumption | use |
| currently | now | | currently | now |
--- ---
## D ## D
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | -------------------- | ------------------ |
| deduct | take off | | deduct | take off |
| deem | treat as, consider | | deem | treat as, consider |
| defer | delay, put off | | defer | delay, put off |
| deficiency | lack | | deficiency | lack |
| delete | remove, cross out | | delete | remove, cross out |
| demonstrate | show, prove | | demonstrate | show, prove |
| denote | show, mean | | denote | show, mean |
| designate | name, appoint | | designate | name, appoint |
| despatch/dispatch | send | | despatch/dispatch | send |
| determine | decide, find out | | determine | decide, find out |
| detrimental | harmful | | detrimental | harmful |
| diminish | reduce, lessen | | diminish | reduce, lessen |
| discontinue | stop | | discontinue | stop |
| disseminate | spread, distribute | | disseminate | spread, distribute |
| documentation | papers, documents | | documentation | papers, documents |
| due to the fact that | because | | due to the fact that | because |
| duration | time, length | | duration | time, length |
| dwelling | home | | dwelling | home |
--- ---
## E ## E
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ----------- | ------------------ |
| economical | cheap, good value | | economical | cheap, good value |
| eligible | allowed, qualified | | eligible | allowed, qualified |
| elucidate | explain | | elucidate | explain |
| enable | allow | | enable | allow |
| encounter | meet | | encounter | meet |
| endeavour | try | | endeavour | try |
| enquire | ask | | enquire | ask |
| ensure | make sure | | ensure | make sure |
| entitlement | right | | entitlement | right |
| envisage | expect | | envisage | expect |
| equivalent | equal, the same | | equivalent | equal, the same |
| erroneous | wrong | | erroneous | wrong |
| establish | set up, show | | establish | set up, show |
| evaluate | assess, test | | evaluate | assess, test |
| excessive | too much | | excessive | too much |
| exclusively | only | | exclusively | only |
| exempt | free from | | exempt | free from |
| expedite | speed up | | expedite | speed up |
| expenditure | spending | | expenditure | spending |
| expire | run out | | expire | run out |
--- ---
## F ## F
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------------- | ------------------- |
| fabricate | make | | fabricate | make |
| facilitate | help, make possible | | facilitate | help, make possible |
| finalise | finish, complete | | finalise | finish, complete |
| following | after | | following | after |
| for the purpose of | to, for | | for the purpose of | to, for |
| for the reason that | because | | for the reason that | because |
| forthwith | now, at once | | forthwith | now, at once |
| forward | send | | forward | send |
| frequently | often | | frequently | often |
| furnish | give, provide | | furnish | give, provide |
| furthermore | also, and | | furthermore | also, and |
--- ---
## G-H ## G-H
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ---------- | ----------------- |
| generate | produce, create | | generate | produce, create |
| henceforth | from now on | | henceforth | from now on |
| hitherto | until now | | hitherto | until now |
--- ---
## I ## I
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ---------------------------- | ----------------- |
| if and when | if, when | | if and when | if, when |
| illustrate | show | | illustrate | show |
| immediately | at once, now | | immediately | at once, now |
| implement | carry out, do | | implement | carry out, do |
| imply | suggest | | imply | suggest |
| in accordance with | under, following | | in accordance with | under, following |
| in addition to | and, also | | in addition to | and, also |
| in conjunction with | with | | in conjunction with | with |
| in excess of | more than | | in excess of | more than |
| in lieu of | instead of | | in lieu of | instead of |
| in order to | to | | in order to | to |
| in receipt of | receive | | in receipt of | receive |
| in relation to | about | | in relation to | about |
| in respect of | about, for | | in respect of | about, for |
| in the event of | if | | in the event of | if |
| in the majority of instances | most, usually | | in the majority of instances | most, usually |
| in the near future | soon | | in the near future | soon |
| in view of the fact that | because | | in view of the fact that | because |
| inception | start | | inception | start |
| indicate | show, suggest | | indicate | show, suggest |
| inform | tell | | inform | tell |
| initiate | start, begin | | initiate | start, begin |
| insert | put in | | insert | put in |
| instances | cases | | instances | cases |
| irrespective of | despite | | irrespective of | despite |
| issue | give, send | | issue | give, send |
--- ---
## L-M ## L-M
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------------- | ------------------ |
| (a) large number of | many | | (a) large number of | many |
| liaise with | work with, talk to | | liaise with | work with, talk to |
| locality | place, area | | locality | place, area |
| locate | find | | locate | find |
| magnitude | size | | magnitude | size |
| (it is) mandatory | (you) must | | (it is) mandatory | (you) must |
| manner | way | | manner | way |
| modification | change | | modification | change |
| moreover | also, and | | moreover | also, and |
--- ---
## N-O ## N-O
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | --------------------- | ----------------- |
| negligible | small | | negligible | small |
| nevertheless | but, however | | nevertheless | but, however |
| notify | tell | | notify | tell |
| notwithstanding | despite, even if | | notwithstanding | despite, even if |
| numerous | many | | numerous | many |
| objective | aim, goal | | objective | aim, goal |
| (it is) obligatory | (you) must | | (it is) obligatory | (you) must |
| obtain | get | | obtain | get |
| occasioned by | caused by | | occasioned by | caused by |
| on behalf of | for | | on behalf of | for |
| on numerous occasions | often | | on numerous occasions | often |
| on receipt of | when you get | | on receipt of | when you get |
| on the grounds that | because | | on the grounds that | because |
| operate | work, run | | operate | work, run |
| optimum | best | | optimum | best |
| option | choice | | option | choice |
| otherwise | or | | otherwise | or |
| outstanding | unpaid | | outstanding | unpaid |
| owing to | because | | owing to | because |
--- ---
## P ## P
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------- | ----------------- |
| partially | partly | | partially | partly |
| participate | take part | | participate | take part |
| particulars | details | | particulars | details |
| per annum | a year | | per annum | a year |
| perform | do | | perform | do |
| permit | let, allow | | permit | let, allow |
| personnel | staff, people | | personnel | staff, people |
| peruse | read | | peruse | read |
| possess | have, own | | possess | have, own |
| practically | almost | | practically | almost |
| predominant | main | | predominant | main |
| prescribe | set | | prescribe | set |
| preserve | keep | | preserve | keep |
| previous | earlier, before | | previous | earlier, before |
| principal | main | | principal | main |
| prior to | before | | prior to | before |
| proceed | go ahead | | proceed | go ahead |
| procure | get | | procure | get |
| prohibit | ban, stop | | prohibit | ban, stop |
| promptly | quickly | | promptly | quickly |
| provide | give | | provide | give |
| provided that | if | | provided that | if |
| provisions | rules, terms | | provisions | rules, terms |
| proximity | nearness | | proximity | nearness |
| purchase | buy | | purchase | buy |
| pursuant to | under | | pursuant to | under |
--- ---
## R ## R
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | -------------- | ----------------- |
| reconsider | think again | | reconsider | think again |
| reduction | cut | | reduction | cut |
| referred to as | called | | referred to as | called |
| regarding | about | | regarding | about |
| reimburse | repay | | reimburse | repay |
| reiterate | repeat | | reiterate | repeat |
| relating to | about | | relating to | about |
| remain | stay | | remain | stay |
| remainder | rest | | remainder | rest |
| remuneration | pay | | remuneration | pay |
| render | make, give | | render | make, give |
| represent | stand for | | represent | stand for |
| request | ask | | request | ask |
| require | need | | require | need |
| residence | home | | residence | home |
| retain | keep | | retain | keep |
| revised | changed, new | | revised | changed, new |
--- ---
## S ## S
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ------------- | ----------------- |
| scrutinise | examine, check | | scrutinise | examine, check |
| select | choose | | select | choose |
| solely | only | | solely | only |
| specified | given, stated | | specified | given, stated |
| state | say | | state | say |
| statutory | legal, by law | | statutory | legal, by law |
| subject to | depending on | | subject to | depending on |
| submit | send, give | | submit | send, give |
| subsequent to | after | | subsequent to | after |
| subsequently | later | | subsequently | later |
| substantial | large, much | | substantial | large, much |
| sufficient | enough | | sufficient | enough |
| supplement | add to | | supplement | add to |
| supplementary | extra | | supplementary | extra |
--- ---
## T-U ## T-U
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ---------- | ----------------- |
| terminate | end, stop | | terminate | end, stop |
| thereafter | then | | thereafter | then |
| thereby | by this | | thereby | by this |
| thus | so | | thus | so |
| to date | so far | | to date | so far |
| transfer | move | | transfer | move |
| transmit | send | | transmit | send |
| ultimately | in the end | | ultimately | in the end |
| undertake | agree, do | | undertake | agree, do |
| uniform | same | | uniform | same |
| utilise | use | | utilise | use |
--- ---
## V-Z ## V-Z
| Complex | Plain Alternative | | Complex | Plain Alternative |
|---------|-------------------| | ----------------- | ----------------- |
| variation | change | | variation | change |
| virtually | almost | | virtually | almost |
| visualise | imagine, see | | visualise | imagine, see |
| ways and means | ways | | ways and means | ways |
| whatsoever | any | | whatsoever | any |
| with a view to | to | | with a view to | to |
| with effect from | from | | with effect from | from |
| with reference to | about | | with reference to | about |
| with regard to | about | | with regard to | about |
| with respect to | about | | with respect to | about |
| zone | area | | zone | area |
--- ---
@@ -16,22 +16,26 @@ If `.mosaic/product-marketing-context.md` exists, read it before asking question
Gather this context (ask if not provided): Gather this context (ask if not provided):
### 1. Page Purpose ### 1. Page Purpose
- What type of page? (homepage, landing page, pricing, feature, about) - What type of page? (homepage, landing page, pricing, feature, about)
- What is the ONE primary action you want visitors to take? - What is the ONE primary action you want visitors to take?
### 2. Audience ### 2. Audience
- Who is the ideal customer? - Who is the ideal customer?
- What problem are they trying to solve? - What problem are they trying to solve?
- What objections or hesitations do they have? - What objections or hesitations do they have?
- What language do they use to describe their problem? - What language do they use to describe their problem?
### 3. Product/Offer ### 3. Product/Offer
- What are you selling or offering? - What are you selling or offering?
- What makes it different from alternatives? - What makes it different from alternatives?
- What's the key transformation or outcome? - What's the key transformation or outcome?
- Any proof points (numbers, testimonials, case studies)? - Any proof points (numbers, testimonials, case studies)?
### 4. Context ### 4. Context
- Where is traffic coming from? (ads, organic, email) - Where is traffic coming from? (ads, organic, email)
- What do visitors already know before arriving? - What do visitors already know before arriving?
@@ -40,19 +44,24 @@ Gather this context (ask if not provided):
## Copywriting Principles ## Copywriting Principles
### Clarity Over Cleverness ### Clarity Over Cleverness
If you have to choose between clear and creative, choose clear. If you have to choose between clear and creative, choose clear.
### Benefits Over Features ### Benefits Over Features
Features: What it does. Benefits: What that means for the customer. Features: What it does. Benefits: What that means for the customer.
### Specificity Over Vagueness ### Specificity Over Vagueness
- Vague: "Save time on your workflow" - Vague: "Save time on your workflow"
- Specific: "Cut your weekly reporting from 4 hours to 15 minutes" - Specific: "Cut your weekly reporting from 4 hours to 15 minutes"
### Customer Language Over Company Language ### Customer Language Over Company Language
Use words your customers use. Mirror voice-of-customer from reviews, interviews, support tickets. Use words your customers use. Mirror voice-of-customer from reviews, interviews, support tickets.
### One Idea Per Section ### One Idea Per Section
Each section should advance one argument. Build a logical flow down the page. Each section should advance one argument. Build a logical flow down the page.
--- ---
@@ -83,6 +92,7 @@ For thorough line-by-line review, use the **copy-editing** skill after your draf
## Best Practices ## Best Practices
### Be Direct ### Be Direct
Get to the point. Don't bury the value in qualifications. Get to the point. Don't bury the value in qualifications.
❌ Slack lets you share files instantly, from documents to images, directly in your conversations ❌ Slack lets you share files instantly, from documents to images, directly in your conversations
@@ -90,14 +100,18 @@ Get to the point. Don't bury the value in qualifications.
✅ Need to share a screenshot? Send as many documents, images, and audio files as your heart desires. ✅ Need to share a screenshot? Send as many documents, images, and audio files as your heart desires.
### Use Rhetorical Questions ### Use Rhetorical Questions
Questions engage readers and make them think about their own situation. Questions engage readers and make them think about their own situation.
- "Hate returning stuff to Amazon?" - "Hate returning stuff to Amazon?"
- "Tired of chasing approvals?" - "Tired of chasing approvals?"
### Use Analogies When Helpful ### Use Analogies When Helpful
Analogies make abstract concepts concrete and memorable. Analogies make abstract concepts concrete and memorable.
### Pepper in Humor (When Appropriate) ### Pepper in Humor (When Appropriate)
Puns and wit make copy memorable—but only if it fits the brand and doesn't undermine clarity. Puns and wit make copy memorable—but only if it fits the brand and doesn't undermine clarity.
--- ---
@@ -107,11 +121,13 @@ Puns and wit make copy memorable—but only if it fits the brand and doesn't und
### Above the Fold ### Above the Fold
**Headline** **Headline**
- Your single most important message - Your single most important message
- Communicate core value proposition - Communicate core value proposition
- Specific > generic - Specific > generic
**Example formulas:** **Example formulas:**
- "{Achieve outcome} without {pain point}" - "{Achieve outcome} without {pain point}"
- "The {category} for {audience}" - "The {category} for {audience}"
- "Never {unpleasant event} again" - "Never {unpleasant event} again"
@@ -122,24 +138,26 @@ Puns and wit make copy memorable—but only if it fits the brand and doesn't und
**For natural transition phrases**: See [references/natural-transitions.md](references/natural-transitions.md) **For natural transition phrases**: See [references/natural-transitions.md](references/natural-transitions.md)
**Subheadline** **Subheadline**
- Expands on headline - Expands on headline
- Adds specificity - Adds specificity
- 1-2 sentences max - 1-2 sentences max
**Primary CTA** **Primary CTA**
- Action-oriented button text - Action-oriented button text
- Communicate what they get: "Start Free Trial" > "Sign Up" - Communicate what they get: "Start Free Trial" > "Sign Up"
### Core Sections ### Core Sections
| Section | Purpose | | Section | Purpose |
|---------|---------| | ------------------ | ---------------------------------------------- |
| Social Proof | Build credibility (logos, stats, testimonials) | | Social Proof | Build credibility (logos, stats, testimonials) |
| Problem/Pain | Show you understand their situation | | Problem/Pain | Show you understand their situation |
| Solution/Benefits | Connect to outcomes (3-5 key benefits) | | Solution/Benefits | Connect to outcomes (3-5 key benefits) |
| How It Works | Reduce perceived complexity (3-4 steps) | | How It Works | Reduce perceived complexity (3-4 steps) |
| Objection Handling | FAQ, comparisons, guarantees | | Objection Handling | FAQ, comparisons, guarantees |
| Final CTA | Recap value, repeat CTA, risk reversal | | Final CTA | Recap value, repeat CTA, risk reversal |
**For detailed section types and page templates**: See [references/copy-frameworks.md](references/copy-frameworks.md) **For detailed section types and page templates**: See [references/copy-frameworks.md](references/copy-frameworks.md)
@@ -148,9 +166,11 @@ Puns and wit make copy memorable—but only if it fits the brand and doesn't und
## CTA Copy Guidelines ## CTA Copy Guidelines
**Weak CTAs (avoid):** **Weak CTAs (avoid):**
- Submit, Sign Up, Learn More, Click Here, Get Started - Submit, Sign Up, Learn More, Click Here, Get Started
**Strong CTAs (use):** **Strong CTAs (use):**
- Start Free Trial - Start Free Trial
- Get [Specific Thing] - Get [Specific Thing]
- See [Product] in Action - See [Product] in Action
@@ -160,6 +180,7 @@ Puns and wit make copy memorable—but only if it fits the brand and doesn't und
**Formula:** [Action Verb] + [What They Get] + [Qualifier if needed] **Formula:** [Action Verb] + [What They Get] + [Qualifier if needed]
Examples: Examples:
- "Start My Free Trial" - "Start My Free Trial"
- "Get the Complete Checklist" - "Get the Complete Checklist"
- "See Pricing for My Team" - "See Pricing for My Team"
@@ -169,26 +190,31 @@ Examples:
## Page-Specific Guidance ## Page-Specific Guidance
### Homepage ### Homepage
- Serve multiple audiences without being generic - Serve multiple audiences without being generic
- Lead with broadest value proposition - Lead with broadest value proposition
- Provide clear paths for different visitor intents - Provide clear paths for different visitor intents
### Landing Page ### Landing Page
- Single message, single CTA - Single message, single CTA
- Match headline to ad/traffic source - Match headline to ad/traffic source
- Complete argument on one page - Complete argument on one page
### Pricing Page ### Pricing Page
- Help visitors choose the right plan - Help visitors choose the right plan
- Address "which is right for me?" anxiety - Address "which is right for me?" anxiety
- Make recommended plan obvious - Make recommended plan obvious
### Feature Page ### Feature Page
- Connect feature → benefit → outcome - Connect feature → benefit → outcome
- Show use cases and examples - Show use cases and examples
- Clear path to try or buy - Clear path to try or buy
### About Page ### About Page
- Tell the story of why you exist - Tell the story of why you exist
- Connect mission to customer benefit - Connect mission to customer benefit
- Still include a CTA - Still include a CTA
@@ -200,16 +226,19 @@ Examples:
Before writing, establish: Before writing, establish:
**Formality level:** **Formality level:**
- Casual/conversational - Casual/conversational
- Professional but friendly - Professional but friendly
- Formal/enterprise - Formal/enterprise
**Brand personality:** **Brand personality:**
- Playful or serious? - Playful or serious?
- Bold or understated? - Bold or understated?
- Technical or accessible? - Technical or accessible?
Maintain consistency, but adjust intensity: Maintain consistency, but adjust intensity:
- Headlines can be bolder - Headlines can be bolder
- Body copy should be clearer - Body copy should be clearer
- CTAs should be action-oriented - CTAs should be action-oriented
@@ -221,22 +250,29 @@ Maintain consistency, but adjust intensity:
When writing copy, provide: When writing copy, provide:
### Page Copy ### Page Copy
Organized by section: Organized by section:
- Headline, Subheadline, CTA - Headline, Subheadline, CTA
- Section headers and body copy - Section headers and body copy
- Secondary CTAs - Secondary CTAs
### Annotations ### Annotations
For key elements, explain: For key elements, explain:
- Why you made this choice - Why you made this choice
- What principle it applies - What principle it applies
### Alternatives ### Alternatives
For headlines and CTAs, provide 2-3 options: For headlines and CTAs, provide 2-3 options:
- Option A: [copy] — [rationale] - Option A: [copy] — [rationale]
- Option B: [copy] — [rationale] - Option B: [copy] — [rationale]
### Meta Content (if relevant) ### Meta Content (if relevant)
- Page title (for SEO) - Page title (for SEO)
- Meta description - Meta description
@@ -7,15 +7,19 @@ Headline formulas, page section types, and structural templates.
### Outcome-Focused ### Outcome-Focused
**{Achieve desirable outcome} without {pain point}** **{Achieve desirable outcome} without {pain point}**
> Understand how users are really experiencing your site without drowning in numbers > Understand how users are really experiencing your site without drowning in numbers
**{Achieve desirable outcome} by {how product makes it possible}** **{Achieve desirable outcome} by {how product makes it possible}**
> Generate more leads by seeing which companies visit your site > Generate more leads by seeing which companies visit your site
**Turn {input} into {outcome}** **Turn {input} into {outcome}**
> Turn your hard-earned sales into repeat customers > Turn your hard-earned sales into repeat customers
**[Achieve outcome] in [timeframe]** **[Achieve outcome] in [timeframe]**
> Get your tax refund in 10 days > Get your tax refund in 10 days
--- ---
@@ -23,12 +27,15 @@ Headline formulas, page section types, and structural templates.
### Problem-Focused ### Problem-Focused
**Never {unpleasant event} again** **Never {unpleasant event} again**
> Never miss a sales opportunity again > Never miss a sales opportunity again
**{Question highlighting the main pain point}** **{Question highlighting the main pain point}**
> Hate returning stuff to Amazon? > Hate returning stuff to Amazon?
**Stop [pain]. Start [pleasure].** **Stop [pain]. Start [pleasure].**
> Stop chasing invoices. Start getting paid on time. > Stop chasing invoices. Start getting paid on time.
--- ---
@@ -36,12 +43,15 @@ Headline formulas, page section types, and structural templates.
### Audience-Focused ### Audience-Focused
**{Key feature/product type} for {target audience}** **{Key feature/product type} for {target audience}**
> Advanced analytics for Shopify e-commerce > Advanced analytics for Shopify e-commerce
**{Key feature/product type} for {target audience} to {what it's used for}** **{Key feature/product type} for {target audience} to {what it's used for}**
> An online whiteboard for teams to ideate and brainstorm together > An online whiteboard for teams to ideate and brainstorm together
**You don't have to {skills or resources} to {achieve desirable outcome}** **You don't have to {skills or resources} to {achieve desirable outcome}**
> With Ahrefs, you don't have to be an SEO pro to rank higher and get more traffic > With Ahrefs, you don't have to be an SEO pro to rank higher and get more traffic
--- ---
@@ -49,9 +59,11 @@ Headline formulas, page section types, and structural templates.
### Differentiation-Focused ### Differentiation-Focused
**The {opposite of usual process} way to {achieve desirable outcome}** **The {opposite of usual process} way to {achieve desirable outcome}**
> The easiest way to turn your passion into income > The easiest way to turn your passion into income
**The [category] that [key differentiator]** **The [category] that [key differentiator]**
> The CRM that updates itself > The CRM that updates itself
--- ---
@@ -59,9 +71,11 @@ Headline formulas, page section types, and structural templates.
### Proof-Focused ### Proof-Focused
**[Number] [people] use [product] to [outcome]** **[Number] [people] use [product] to [outcome]**
> 50,000 marketers use Drip to send better emails > 50,000 marketers use Drip to send better emails
**{Key benefit of your product}** **{Key benefit of your product}**
> Sound clear in online meetings > Sound clear in online meetings
--- ---
@@ -69,27 +83,35 @@ Headline formulas, page section types, and structural templates.
### Additional Formulas ### Additional Formulas
**The simple way to {outcome}** **The simple way to {outcome}**
> The simple way to track your time > The simple way to track your time
**Finally, {category} that {benefit}** **Finally, {category} that {benefit}**
> Finally, accounting software that doesn't suck > Finally, accounting software that doesn't suck
**{Outcome} without {common pain}** **{Outcome} without {common pain}**
> Build your website without writing code > Build your website without writing code
**Get {benefit} from your {thing}** **Get {benefit} from your {thing}**
> Get more revenue from your existing traffic > Get more revenue from your existing traffic
**{Action verb} your {thing} like {admirable example}** **{Action verb} your {thing} like {admirable example}**
> Market your SaaS like a Fortune 500 > Market your SaaS like a Fortune 500
**What if you could {desirable outcome}?** **What if you could {desirable outcome}?**
> What if you could close deals 30% faster? > What if you could close deals 30% faster?
**Everything you need to {outcome}** **Everything you need to {outcome}**
> Everything you need to launch your course > Everything you need to launch your course
**The {adjective} {category} built for {audience}** **The {adjective} {category} built for {audience}**
> The lightweight CRM built for startups > The lightweight CRM built for startups
--- ---
@@ -99,33 +121,39 @@ Headline formulas, page section types, and structural templates.
### Core Sections ### Core Sections
**Hero (Above the Fold)** **Hero (Above the Fold)**
- Headline + subheadline - Headline + subheadline
- Primary CTA - Primary CTA
- Supporting visual (product screenshot, hero image) - Supporting visual (product screenshot, hero image)
- Optional: Social proof bar - Optional: Social proof bar
**Social Proof Bar** **Social Proof Bar**
- Customer logos (recognizable > many) - Customer logos (recognizable > many)
- Key metric ("10,000+ teams") - Key metric ("10,000+ teams")
- Star rating with review count - Star rating with review count
- Short testimonial snippet - Short testimonial snippet
**Problem/Pain Section** **Problem/Pain Section**
- Articulate their problem better than they can - Articulate their problem better than they can
- Create recognition ("that's exactly my situation") - Create recognition ("that's exactly my situation")
- Hint at cost of not solving it - Hint at cost of not solving it
**Solution/Benefits Section** **Solution/Benefits Section**
- Bridge from problem to your solution - Bridge from problem to your solution
- 3-5 key benefits (not 10) - 3-5 key benefits (not 10)
- Each: headline + explanation + proof if available - Each: headline + explanation + proof if available
**How It Works** **How It Works**
- 3-4 numbered steps - 3-4 numbered steps
- Reduces perceived complexity - Reduces perceived complexity
- Each step: action + outcome - Each step: action + outcome
**Final CTA Section** **Final CTA Section**
- Recap value proposition - Recap value proposition
- Repeat primary CTA - Repeat primary CTA
- Risk reversal (guarantee, free trial) - Risk reversal (guarantee, free trial)
@@ -135,67 +163,79 @@ Headline formulas, page section types, and structural templates.
### Supporting Sections ### Supporting Sections
**Testimonials** **Testimonials**
- Full quotes with names, roles, companies - Full quotes with names, roles, companies
- Photos when possible - Photos when possible
- Specific results over vague praise - Specific results over vague praise
- Formats: quote cards, video, tweet embeds - Formats: quote cards, video, tweet embeds
**Case Studies** **Case Studies**
- Problem → Solution → Results - Problem → Solution → Results
- Specific metrics and outcomes - Specific metrics and outcomes
- Customer name and context - Customer name and context
- Can be snippets with "Read more" links - Can be snippets with "Read more" links
**Use Cases** **Use Cases**
- Different ways product is used - Different ways product is used
- Helps visitors self-identify - Helps visitors self-identify
- "For marketers who need X" format - "For marketers who need X" format
**Personas / "Built For" Sections** **Personas / "Built For" Sections**
- Explicitly call out target audience - Explicitly call out target audience
- "Perfect for [role]" blocks - "Perfect for [role]" blocks
- Addresses "Is this for me?" question - Addresses "Is this for me?" question
**FAQ Section** **FAQ Section**
- Address common objections - Address common objections
- Good for SEO - Good for SEO
- Reduces support burden - Reduces support burden
- 5-10 most common questions - 5-10 most common questions
**Comparison Section** **Comparison Section**
- vs. competitors (name them or don't) - vs. competitors (name them or don't)
- vs. status quo (spreadsheets, manual processes) - vs. status quo (spreadsheets, manual processes)
- Tables or side-by-side format - Tables or side-by-side format
**Integrations / Partners** **Integrations / Partners**
- Logos of tools you connect with - Logos of tools you connect with
- "Works with your stack" messaging - "Works with your stack" messaging
- Builds credibility - Builds credibility
**Founder Story / Manifesto** **Founder Story / Manifesto**
- Why you built this - Why you built this
- What you believe - What you believe
- Emotional connection - Emotional connection
- Differentiates from faceless competitors - Differentiates from faceless competitors
**Demo / Product Tour** **Demo / Product Tour**
- Interactive demos - Interactive demos
- Video walkthroughs - Video walkthroughs
- GIF previews - GIF previews
- Shows product in action - Shows product in action
**Pricing Preview** **Pricing Preview**
- Teaser even on non-pricing pages - Teaser even on non-pricing pages
- Starting price or "from $X/mo" - Starting price or "from $X/mo"
- Moves decision-makers forward - Moves decision-makers forward
**Guarantee / Risk Reversal** **Guarantee / Risk Reversal**
- Money-back guarantee - Money-back guarantee
- Free trial terms - Free trial terms
- "Cancel anytime" - "Cancel anytime"
- Reduces friction - Reduces friction
**Stats Section** **Stats Section**
- Key metrics that build credibility - Key metrics that build credibility
- "10,000+ customers" - "10,000+ customers"
- "4.9/5 rating" - "4.9/5 rating"
@@ -295,12 +335,14 @@ Good for ProductHunt, launches, or announcements.
### Problem Section ### Problem Section
Start with phrases like: Start with phrases like:
- "You know the feeling..." - "You know the feeling..."
- "If you're like most [role]..." - "If you're like most [role]..."
- "Every day, [audience] struggles with..." - "Every day, [audience] struggles with..."
- "We've all been there..." - "We've all been there..."
Then describe: Then describe:
- The specific frustration - The specific frustration
- The time/money wasted - The time/money wasted
- The impact on their work/life - The impact on their work/life
@@ -308,6 +350,7 @@ Then describe:
### Benefits Section ### Benefits Section
For each benefit, include: For each benefit, include:
- **Headline**: The outcome they get - **Headline**: The outcome they get
- **Body**: How it works (1-2 sentences) - **Body**: How it works (1-2 sentences)
- **Proof**: Number, testimonial, or example (optional) - **Proof**: Number, testimonial, or example (optional)
@@ -315,11 +358,13 @@ For each benefit, include:
### How It Works Section ### How It Works Section
Each step should be: Each step should be:
- **Numbered**: Creates sense of progress - **Numbered**: Creates sense of progress
- **Simple verb**: "Connect," "Set up," "Get" - **Simple verb**: "Connect," "Set up," "Get"
- **Outcome-oriented**: What they get from this step - **Outcome-oriented**: What they get from this step
Example: Example:
1. Connect your tools (takes 2 minutes) 1. Connect your tools (takes 2 minutes)
2. Set your preferences 2. Set your preferences
3. Get automated reports every Monday 3. Get automated reports every Monday
@@ -327,12 +372,14 @@ Example:
### Testimonial Selection ### Testimonial Selection
Best testimonials include: Best testimonials include:
- Specific results ("increased conversions by 32%") - Specific results ("increased conversions by 32%")
- Before/after context ("We used to spend hours...") - Before/after context ("We used to spend hours...")
- Role + company for credibility - Role + company for credibility
- Something quotable and specific - Something quotable and specific
Avoid testimonials that just say: Avoid testimonials that just say:
- "Great product!" - "Great product!"
- "Love it!" - "Love it!"
- "Easy to use!" - "Easy to use!"
@@ -138,6 +138,7 @@ Use to connect ideas and reinforce key points:
Use when citing sources, data, or expert opinions: Use when citing sources, data, or expert opinions:
### Neutral attribution ### Neutral attribution
- According to [Source],... - According to [Source],...
- [Source] reports that... - [Source] reports that...
- Research shows that... - Research shows that...
@@ -145,12 +146,14 @@ Use when citing sources, data, or expert opinions:
- A study by [Source] found... - A study by [Source] found...
### Expert quotes ### Expert quotes
- As [Expert] puts it,... - As [Expert] puts it,...
- [Expert] explains,... - [Expert] explains,...
- In the words of [Expert],... - In the words of [Expert],...
- [Expert] notes that... - [Expert] notes that...
### Supporting claims ### Supporting claims
- This is backed by... - This is backed by...
- Evidence suggests... - Evidence suggests...
- The numbers confirm... - The numbers confirm...
@@ -121,19 +121,18 @@ export interface Message {
export interface AgentEvents { export interface AgentEvents {
'message:user': (message: Message) => void; 'message:user': (message: Message) => void;
'message:assistant': (message: Message) => void; 'message:assistant': (message: Message) => void;
'item:update': (item: StreamableOutputItem) => void; // Items emitted with same ID, replace by ID 'item:update': (item: StreamableOutputItem) => void; // Items emitted with same ID, replace by ID
'stream:start': () => void; 'stream:start': () => void;
'stream:delta': (delta: string, accumulated: string) => void; 'stream:delta': (delta: string, accumulated: string) => void;
'stream:end': (fullText: string) => void; 'stream:end': (fullText: string) => void;
'tool:call': (name: string, args: unknown) => void; 'tool:call': (name: string, args: unknown) => void;
'tool:result': (name: string, result: unknown) => void; 'tool:result': (name: string, result: unknown) => void;
'reasoning:update': (text: string) => void; // Extended thinking content 'reasoning:update': (text: string) => void; // Extended thinking content
'error': (error: Error) => void; error: (error: Error) => void;
'thinking:start': () => void; 'thinking:start': () => void;
'thinking:end': () => void; 'thinking:end': () => void;
} }
// Agent configuration // Agent configuration
export interface AgentConfig { export interface AgentConfig {
apiKey: string; apiKey: string;
@@ -211,7 +210,9 @@ export class Agent extends EventEmitter<AgentEvents> {
switch (item.type) { switch (item.type) {
case 'message': case 'message':
// Message items contain progressively updated content // Message items contain progressively updated content
const textContent = item.content?.find((c: { type: string }) => c.type === 'output_text'); const textContent = item.content?.find(
(c: { type: string }) => c.type === 'output_text',
);
if (textContent && 'text' in textContent) { if (textContent && 'text' in textContent) {
const newText = textContent.text; const newText = textContent.text;
if (newText !== fullText) { if (newText !== fullText) {
@@ -232,7 +233,9 @@ export class Agent extends EventEmitter<AgentEvents> {
break; break;
case 'reasoning': case 'reasoning':
// Extended thinking/reasoning content // Extended thinking/reasoning content
const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text'); const reasoningText = item.content?.find(
(c: { type: string }) => c.type === 'reasoning_text',
);
if (reasoningText && 'text' in reasoningText) { if (reasoningText && 'text' in reasoningText) {
this.emit('reasoning:update', reasoningText.text); this.emit('reasoning:update', reasoningText.text);
} }
@@ -426,7 +429,9 @@ function ItemRenderer({ item }: { item: StreamableOutputItem }) {
const text = textContent && 'text' in textContent ? textContent.text : ''; const text = textContent && 'text' in textContent ? textContent.text : '';
return ( return (
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold color="green">◀ Assistant</Text> <Text bold color="green">
◀ Assistant
</Text>
<Text wrap="wrap">{text}</Text> <Text wrap="wrap">{text}</Text>
{item.status !== 'completed' && <Text color="gray">▌</Text>} {item.status !== 'completed' && <Text color="gray">▌</Text>}
</Box> </Box>
@@ -440,12 +445,18 @@ function ItemRenderer({ item }: { item: StreamableOutputItem }) {
</Text> </Text>
); );
case 'reasoning': { case 'reasoning': {
const reasoningText = item.content?.find((c: { type: string }) => c.type === 'reasoning_text'); const reasoningText = item.content?.find(
(c: { type: string }) => c.type === 'reasoning_text',
);
const text = reasoningText && 'text' in reasoningText ? reasoningText.text : ''; const text = reasoningText && 'text' in reasoningText ? reasoningText.text : '';
return ( return (
<Box flexDirection="column" marginBottom={1}> <Box flexDirection="column" marginBottom={1}>
<Text bold color="magenta">💭 Thinking</Text> <Text bold color="magenta">
<Text wrap="wrap" color="gray">{text}</Text> 💭 Thinking
</Text>
<Text wrap="wrap" color="gray">
{text}
</Text>
</Box> </Box>
); );
} }
@@ -539,7 +550,9 @@ function App() {
return ( return (
<Box flexDirection="column" padding={1}> <Box flexDirection="column" padding={1}>
<Box marginBottom={1}> <Box marginBottom={1}>
<Text bold color="magenta">🤖 OpenRouter Agent</Text> <Text bold color="magenta">
🤖 OpenRouter Agent
</Text>
<Text color="gray"> (Esc to exit)</Text> <Text color="gray"> (Esc to exit)</Text>
</Box> </Box>
@@ -556,12 +569,7 @@ function App() {
</Box> </Box>
<Box borderStyle="single" borderColor="gray" paddingX={1}> <Box borderStyle="single" borderColor="gray" paddingX={1}>
<InputField <InputField value={input} onChange={setInput} onSubmit={sendMessage} disabled={isLoading} />
value={input}
onChange={setInput}
onSubmit={sendMessage}
disabled={isLoading}
/>
</Box> </Box>
</Box> </Box>
); );
@@ -601,24 +609,27 @@ For function calls, arguments stream progressively:
### Why Items Are Better ### Why Items Are Better
**Traditional (accumulation required):** **Traditional (accumulation required):**
```typescript ```typescript
let text = ''; let text = '';
for await (const chunk of result.getTextStream()) { for await (const chunk of result.getTextStream()) {
text += chunk; // Manual accumulation text += chunk; // Manual accumulation
updateUI(text); updateUI(text);
} }
``` ```
**Items (complete replacement):** **Items (complete replacement):**
```typescript ```typescript
const items = new Map<string, StreamableOutputItem>(); const items = new Map<string, StreamableOutputItem>();
for await (const item of result.getItemsStream()) { for await (const item of result.getItemsStream()) {
items.set(item.id, item); // Replace by ID items.set(item.id, item); // Replace by ID
updateUI(items); updateUI(items);
} }
``` ```
Benefits: Benefits:
- **No manual chunk management** - each item is complete - **No manual chunk management** - each item is complete
- **Handles concurrent outputs** - function calls and messages can stream in parallel - **Handles concurrent outputs** - function calls and messages can stream in parallel
- **Full TypeScript inference** for all item types - **Full TypeScript inference** for all item types
@@ -710,55 +721,55 @@ discord.login(process.env.DISCORD_TOKEN);
### Constructor Options ### Constructor Options
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| | ------------ | ------ | ------------------------------ | --------------------------- |
| apiKey | string | required | OpenRouter API key | | apiKey | string | required | OpenRouter API key |
| model | string | 'openrouter/auto' | Model to use | | model | string | 'openrouter/auto' | Model to use |
| instructions | string | 'You are a helpful assistant.' | System prompt | | instructions | string | 'You are a helpful assistant.' | System prompt |
| tools | Tool[] | [] | Available tools | | tools | Tool[] | [] | Available tools |
| maxSteps | number | 5 | Max agentic loop iterations | | maxSteps | number | 5 | Max agentic loop iterations |
### Methods ### Methods
| Method | Returns | Description | | Method | Returns | Description |
|--------|---------|-------------| | ----------------------- | --------------- | ------------------------------ |
| `send(content)` | Promise<string> | Send message with streaming | | `send(content)` | Promise<string> | Send message with streaming |
| `sendSync(content)` | Promise<string> | Send message without streaming | | `sendSync(content)` | Promise<string> | Send message without streaming |
| `getMessages()` | Message[] | Get conversation history | | `getMessages()` | Message[] | Get conversation history |
| `clearHistory()` | void | Clear conversation | | `clearHistory()` | void | Clear conversation |
| `setInstructions(text)` | void | Update system prompt | | `setInstructions(text)` | void | Update system prompt |
| `addTool(tool)` | void | Add tool at runtime | | `addTool(tool)` | void | Add tool at runtime |
### Events ### Events
| Event | Payload | Description | | Event | Payload | Description |
|-------|---------|-------------| | ------------------- | -------------------- | ---------------------------------------------- |
| `message:user` | Message | User message added | | `message:user` | Message | User message added |
| `message:assistant` | Message | Assistant response complete | | `message:assistant` | Message | Assistant response complete |
| `item:update` | StreamableOutputItem | Item emitted (replace by ID, don't accumulate) | | `item:update` | StreamableOutputItem | Item emitted (replace by ID, don't accumulate) |
| `stream:start` | - | Streaming started | | `stream:start` | - | Streaming started |
| `stream:delta` | (delta, accumulated) | New text chunk | | `stream:delta` | (delta, accumulated) | New text chunk |
| `stream:end` | fullText | Streaming complete | | `stream:end` | fullText | Streaming complete |
| `tool:call` | (name, args) | Tool being called | | `tool:call` | (name, args) | Tool being called |
| `tool:result` | (name, result) | Tool returned result | | `tool:result` | (name, result) | Tool returned result |
| `reasoning:update` | text | Extended thinking content | | `reasoning:update` | text | Extended thinking content |
| `thinking:start` | - | Agent processing | | `thinking:start` | - | Agent processing |
| `thinking:end` | - | Agent done processing | | `thinking:end` | - | Agent done processing |
| `error` | Error | Error occurred | | `error` | Error | Error occurred |
### Item Types (from getItemsStream) ### Item Types (from getItemsStream)
The SDK uses an items-based streaming model where items are emitted multiple times with the same ID but progressively updated content. Replace items by their ID rather than accumulating chunks. The SDK uses an items-based streaming model where items are emitted multiple times with the same ID but progressively updated content. Replace items by their ID rather than accumulating chunks.
| Type | Purpose | | Type | Purpose |
|------|---------| | ----------------------- | ----------------------------------------- |
| `message` | Assistant text responses | | `message` | Assistant text responses |
| `function_call` | Tool invocations with streaming arguments | | `function_call` | Tool invocations with streaming arguments |
| `function_call_output` | Results from executed tools | | `function_call_output` | Results from executed tools |
| `reasoning` | Extended thinking content | | `reasoning` | Extended thinking content |
| `web_search_call` | Web search operations | | `web_search_call` | Web search operations |
| `file_search_call` | File search operations | | `file_search_call` | File search operations |
| `image_generation_call` | Image generation operations | | `image_generation_call` | Image generation operations |
## Discovering Models ## Discovering Models
@@ -784,8 +795,8 @@ async function fetchModels(): Promise<OpenRouterModel[]> {
// Find models by criteria // Find models by criteria
async function findModels(filter: { async function findModels(filter: {
author?: string; // e.g., 'anthropic', 'openai', 'google' author?: string; // e.g., 'anthropic', 'openai', 'google'
minContext?: number; // e.g., 100000 for 100k context minContext?: number; // e.g., 100000 for 100k context
maxPromptPrice?: number; // e.g., 0.001 for cheap models maxPromptPrice?: number; // e.g., 0.001 for cheap models
}): Promise<OpenRouterModel[]> { }): Promise<OpenRouterModel[]> {
const models = await fetchModels(); const models = await fetchModels();
@@ -821,7 +832,7 @@ const bestModel = models.find((m) => m.id.includes('claude')) || models[0];
const agent = createAgent({ const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!, apiKey: process.env.OPENROUTER_API_KEY!,
model: bestModel.id, // Use discovered model model: bestModel.id, // Use discovered model
instructions: 'You are a helpful assistant.', instructions: 'You are a helpful assistant.',
}); });
``` ```
@@ -834,7 +845,7 @@ available model for your request:
```typescript ```typescript
const agent = createAgent({ const agent = createAgent({
apiKey: process.env.OPENROUTER_API_KEY!, apiKey: process.env.OPENROUTER_API_KEY!,
model: 'openrouter/auto', // Auto-selects best model model: 'openrouter/auto', // Auto-selects best model
}); });
``` ```
@@ -73,15 +73,18 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
**Location:** `lib/auth.ts` or `src/lib/auth.ts` **Location:** `lib/auth.ts` or `src/lib/auth.ts`
**Minimal config needs:** **Minimal config needs:**
- `database` - Connection or adapter - `database` - Connection or adapter
- `emailAndPassword: { enabled: true }` - For email/password auth - `emailAndPassword: { enabled: true }` - For email/password auth
**Standard config adds:** **Standard config adds:**
- `socialProviders` - OAuth providers (google, github, etc.) - `socialProviders` - OAuth providers (google, github, etc.)
- `emailVerification.sendVerificationEmail` - Email verification handler - `emailVerification.sendVerificationEmail` - Email verification handler
- `emailAndPassword.sendResetPassword` - Password reset handler - `emailAndPassword.sendResetPassword` - Password reset handler
**Full config adds:** **Full config adds:**
- `plugins` - Array of feature plugins - `plugins` - Array of feature plugins
- `session` - Expiry, cookie cache settings - `session` - Expiry, cookie cache settings
- `account.accountLinking` - Multi-provider linking - `account.accountLinking` - Multi-provider linking
@@ -110,14 +113,14 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
## Route Handler Setup ## Route Handler Setup
| Framework | File | Handler | | Framework | File | Handler |
|-----------|------|---------| | ------------------ | -------------------------------- | ------------------------------------------------ |
| Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` | | Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` |
| Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export | | Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export |
| Express | Any file | `app.all("/api/auth/*", toNodeHandler(auth))` | | Express | Any file | `app.all("/api/auth/*", toNodeHandler(auth))` |
| SvelteKit | `src/hooks.server.ts` | `svelteKitHandler(auth)` | | SvelteKit | `src/hooks.server.ts` | `svelteKitHandler(auth)` |
| SolidStart | Route file | `solidStartHandler(auth)` | | SolidStart | Route file | `solidStartHandler(auth)` |
| Hono | Route file | `auth.handler(c.req.raw)` | | Hono | Route file | `auth.handler(c.req.raw)` |
**Next.js Server Components:** Add `nextCookies()` plugin to auth config. **Next.js Server Components:** Add `nextCookies()` plugin to auth config.
@@ -125,11 +128,11 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
## Database Migrations ## Database Migrations
| Adapter | Command | | Adapter | Command |
|---------|---------| | --------------- | -------------------------------------------------------------------------------------------------- |
| Built-in Kysely | `npx @better-auth/cli@latest migrate` (applies directly) | | Built-in Kysely | `npx @better-auth/cli@latest migrate` (applies directly) |
| Prisma | `npx @better-auth/cli@latest generate --output prisma/schema.prisma` then `npx prisma migrate dev` | | Prisma | `npx @better-auth/cli@latest generate --output prisma/schema.prisma` then `npx prisma migrate dev` |
| Drizzle | `npx @better-auth/cli@latest generate --output src/db/auth-schema.ts` then `npx drizzle-kit push` | | Drizzle | `npx @better-auth/cli@latest generate --output src/db/auth-schema.ts` then `npx drizzle-kit push` |
**Re-run after adding plugins.** **Re-run after adding plugins.**
@@ -137,28 +140,28 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
## Database Adapters ## Database Adapters
| Database | Setup | | Database | Setup |
|----------|-------| | ---------- | -------------------------------------------------------------------------------------- |
| SQLite | Pass `better-sqlite3` or `bun:sqlite` instance directly | | SQLite | Pass `better-sqlite3` or `bun:sqlite` instance directly |
| PostgreSQL | Pass `pg.Pool` instance directly | | PostgreSQL | Pass `pg.Pool` instance directly |
| MySQL | Pass `mysql2` pool directly | | MySQL | Pass `mysql2` pool directly |
| Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` | | Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` |
| Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` | | Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` |
| MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` | | MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` |
--- ---
## Common Plugins ## Common Plugins
| Plugin | Server Import | Client Import | Purpose | | Plugin | Server Import | Client Import | Purpose |
|--------|---------------|---------------|---------| | -------------- | ---------------------- | -------------------- | ----------------- |
| `twoFactor` | `better-auth/plugins` | `twoFactorClient` | 2FA with TOTP/OTP | | `twoFactor` | `better-auth/plugins` | `twoFactorClient` | 2FA with TOTP/OTP |
| `organization` | `better-auth/plugins` | `organizationClient` | Teams/orgs | | `organization` | `better-auth/plugins` | `organizationClient` | Teams/orgs |
| `admin` | `better-auth/plugins` | `adminClient` | User management | | `admin` | `better-auth/plugins` | `adminClient` | User management |
| `bearer` | `better-auth/plugins` | - | API token auth | | `bearer` | `better-auth/plugins` | - | API token auth |
| `openAPI` | `better-auth/plugins` | - | API docs | | `openAPI` | `better-auth/plugins` | - | API docs |
| `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn | | `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn |
| `sso` | `@better-auth/sso` | - | Enterprise SSO | | `sso` | `@better-auth/sso` | - | Enterprise SSO |
**Plugin pattern:** Server plugin + client plugin + run migrations. **Plugin pattern:** Server plugin + client plugin + run migrations.
@@ -167,6 +170,7 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
## Auth UI Implementation ## Auth UI Implementation
**Sign in flow:** **Sign in flow:**
1. `signIn.email({ email, password })` or `signIn.social({ provider, callbackURL })` 1. `signIn.email({ email, password })` or `signIn.social({ provider, callbackURL })`
2. Handle `error` in response 2. Handle `error` in response
3. Redirect on success 3. Redirect on success
@@ -195,13 +199,13 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
## Troubleshooting ## Troubleshooting
| Issue | Fix | | Issue | Fix |
|-------|-----| | ------------------------------- | ------------------------------------------------------------- |
| "Secret not set" | Add `BETTER_AUTH_SECRET` env var | | "Secret not set" | Add `BETTER_AUTH_SECRET` env var |
| "Invalid Origin" | Add domain to `trustedOrigins` | | "Invalid Origin" | Add domain to `trustedOrigins` |
| Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod | | Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod |
| OAuth callback errors | Verify redirect URIs in provider dashboard | | OAuth callback errors | Verify redirect URIs in provider dashboard |
| Type errors after adding plugin | Re-run CLI generate/migrate | | Type errors after adding plugin | Re-run CLI generate/migrate |
--- ---
@@ -211,4 +215,4 @@ Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE
- [Examples](https://github.com/better-auth/examples) - [Examples](https://github.com/better-auth/examples)
- [Plugins](https://better-auth.com/docs/concepts/plugins) - [Plugins](https://better-auth.com/docs/concepts/plugins)
- [CLI](https://better-auth.com/docs/concepts/cli) - [CLI](https://better-auth.com/docs/concepts/cli)
- [Migration Guides](https://better-auth.com/docs/guides) - [Migration Guides](https://better-auth.com/docs/guides)
@@ -32,12 +32,14 @@ digraph when_to_use {
``` ```
**Use when:** **Use when:**
- 3+ test files failing with different root causes - 3+ test files failing with different root causes
- Multiple subsystems broken independently - Multiple subsystems broken independently
- Each problem can be understood without context from others - Each problem can be understood without context from others
- No shared state between investigations - No shared state between investigations
**Don't use when:** **Don't use when:**
- Failures are related (fix one might fix others) - Failures are related (fix one might fix others)
- Need to understand full system state - Need to understand full system state
- Agents would interfere with each other - Agents would interfere with each other
@@ -47,6 +49,7 @@ digraph when_to_use {
### 1. Identify Independent Domains ### 1. Identify Independent Domains
Group failures by what's broken: Group failures by what's broken:
- File A tests: Tool approval flow - File A tests: Tool approval flow
- File B tests: Batch completion behavior - File B tests: Batch completion behavior
- File C tests: Abort functionality - File C tests: Abort functionality
@@ -56,6 +59,7 @@ Each domain is independent - fixing tool approval doesn't affect abort tests.
### 2. Create Focused Agent Tasks ### 2. Create Focused Agent Tasks
Each agent gets: Each agent gets:
- **Specific scope:** One test file or subsystem - **Specific scope:** One test file or subsystem
- **Clear goal:** Make these tests pass - **Clear goal:** Make these tests pass
- **Constraints:** Don't change other code - **Constraints:** Don't change other code
@@ -65,15 +69,16 @@ Each agent gets:
```typescript ```typescript
// In Claude Code / AI environment // In Claude Code / AI environment
Task("Fix agent-tool-abort.test.ts failures") Task('Fix agent-tool-abort.test.ts failures');
Task("Fix batch-completion-behavior.test.ts failures") Task('Fix batch-completion-behavior.test.ts failures');
Task("Fix tool-approval-race-conditions.test.ts failures") Task('Fix tool-approval-race-conditions.test.ts failures');
// All three run concurrently // All three run concurrently
``` ```
### 4. Review and Integrate ### 4. Review and Integrate
When agents return: When agents return:
- Read each summary - Read each summary
- Verify fixes don't conflict - Verify fixes don't conflict
- Run full test suite - Run full test suite
@@ -82,6 +87,7 @@ When agents return:
## Agent Prompt Structure ## Agent Prompt Structure
Good agent prompts are: Good agent prompts are:
1. **Focused** - One clear problem domain 1. **Focused** - One clear problem domain
2. **Self-contained** - All context needed to understand the problem 2. **Self-contained** - All context needed to understand the problem
3. **Specific about output** - What should the agent return? 3. **Specific about output** - What should the agent return?
@@ -133,6 +139,7 @@ Return: Summary of what you found and what you fixed.
**Scenario:** 6 test failures across 3 files after major refactoring **Scenario:** 6 test failures across 3 files after major refactoring
**Failures:** **Failures:**
- agent-tool-abort.test.ts: 3 failures (timing issues) - agent-tool-abort.test.ts: 3 failures (timing issues)
- batch-completion-behavior.test.ts: 2 failures (tools not executing) - batch-completion-behavior.test.ts: 2 failures (tools not executing)
- tool-approval-race-conditions.test.ts: 1 failure (execution count = 0) - tool-approval-race-conditions.test.ts: 1 failure (execution count = 0)
@@ -140,6 +147,7 @@ Return: Summary of what you found and what you fixed.
**Decision:** Independent domains - abort logic separate from batch completion separate from race conditions **Decision:** Independent domains - abort logic separate from batch completion separate from race conditions
**Dispatch:** **Dispatch:**
``` ```
Agent 1 → Fix agent-tool-abort.test.ts Agent 1 → Fix agent-tool-abort.test.ts
Agent 2 → Fix batch-completion-behavior.test.ts Agent 2 → Fix batch-completion-behavior.test.ts
@@ -147,6 +155,7 @@ Agent 3 → Fix tool-approval-race-conditions.test.ts
``` ```
**Results:** **Results:**
- Agent 1: Replaced timeouts with event-based waiting - Agent 1: Replaced timeouts with event-based waiting
- Agent 2: Fixed event structure bug (threadId in wrong place) - Agent 2: Fixed event structure bug (threadId in wrong place)
- Agent 3: Added wait for async tool execution to complete - Agent 3: Added wait for async tool execution to complete
@@ -165,6 +174,7 @@ Agent 3 → Fix tool-approval-race-conditions.test.ts
## Verification ## Verification
After agents return: After agents return:
1. **Review each summary** - Understand what changed 1. **Review each summary** - Understand what changed
2. **Check for conflicts** - Did agents edit same code? 2. **Check for conflicts** - Did agents edit same code?
3. **Run full suite** - Verify all fixes work together 3. **Run full suite** - Verify all fixes work together
@@ -173,6 +183,7 @@ After agents return:
## Real-World Impact ## Real-World Impact
From debugging session (2025-10-03): From debugging session (2025-10-03):
- 6 failures across 3 files - 6 failures across 3 files
- 3 agents dispatched in parallel - 3 agents dispatched in parallel
- All investigations completed concurrently - All investigations completed concurrently
@@ -10,6 +10,7 @@ This skill provides a structured workflow for guiding users through collaborativ
## When to Offer This Workflow ## When to Offer This Workflow
**Trigger conditions:** **Trigger conditions:**
- User mentions writing documentation: "write a doc", "draft a proposal", "create a spec", "write up" - User mentions writing documentation: "write a doc", "draft a proposal", "create a spec", "write up"
- User mentions specific doc types: "PRD", "design doc", "decision doc", "RFC" - User mentions specific doc types: "PRD", "design doc", "decision doc", "RFC"
- User seems to be starting a substantial writing task - User seems to be starting a substantial writing task
@@ -42,11 +43,13 @@ Start by asking the user for meta-context about the document:
Inform them they can answer in shorthand or dump information however works best for them. Inform them they can answer in shorthand or dump information however works best for them.
**If user provides a template or mentions a doc type:** **If user provides a template or mentions a doc type:**
- Ask if they have a template document to share - Ask if they have a template document to share
- If they provide a link to a shared document, use the appropriate integration to fetch it - If they provide a link to a shared document, use the appropriate integration to fetch it
- If they provide a file, read it - If they provide a file, read it
**If user mentions editing an existing shared document:** **If user mentions editing an existing shared document:**
- Use the appropriate integration to read the current state - Use the appropriate integration to read the current state
- Check for images without alt-text - Check for images without alt-text
- If images exist without alt-text, explain that when others use Claude to understand the doc, Claude won't be able to see them. Ask if they want alt-text generated. If so, request they paste each image into chat for descriptive alt-text generation. - If images exist without alt-text, explain that when others use Claude to understand the doc, Claude won't be able to see them. Ask if they want alt-text generated. If so, request they paste each image into chat for descriptive alt-text generation.
@@ -54,6 +57,7 @@ Inform them they can answer in shorthand or dump information however works best
### Info Dumping ### Info Dumping
Once initial questions are answered, encourage the user to dump all the context they have. Request information such as: Once initial questions are answered, encourage the user to dump all the context they have. Request information such as:
- Background on the project/problem - Background on the project/problem
- Related team discussions or shared documents - Related team discussions or shared documents
- Why alternative solutions aren't being used - Why alternative solutions aren't being used
@@ -63,6 +67,7 @@ Once initial questions are answered, encourage the user to dump all the context
- Stakeholder concerns - Stakeholder concerns
Advise them not to worry about organizing it - just get it all out. Offer multiple ways to provide context: Advise them not to worry about organizing it - just get it all out. Offer multiple ways to provide context:
- Info dump stream-of-consciousness - Info dump stream-of-consciousness
- Point to team channels or threads to read - Point to team channels or threads to read
- Link to shared documents - Link to shared documents
@@ -107,6 +112,7 @@ If user wants to add more, let them. When ready, proceed to Stage 2.
**Instructions to user:** **Instructions to user:**
Explain that the document will be built section by section. For each section: Explain that the document will be built section by section. For each section:
1. Clarifying questions will be asked about what to include 1. Clarifying questions will be asked about what to include
2. 5-20 options will be brainstormed 2. 5-20 options will be brainstormed
3. User will indicate what to keep/remove/combine 3. User will indicate what to keep/remove/combine
@@ -162,6 +168,7 @@ Inform them they can answer in shorthand or just indicate what's important to co
### Step 2: Brainstorming ### Step 2: Brainstorming
For the [SECTION NAME] section, brainstorm [5-20] things that might be included, depending on the section's complexity. Look for: For the [SECTION NAME] section, brainstorm [5-20] things that might be included, depending on the section's complexity. Look for:
- Context shared that might have been forgotten - Context shared that might have been forgotten
- Angles or considerations not yet mentioned - Angles or considerations not yet mentioned
@@ -172,6 +179,7 @@ Generate 5-20 numbered options based on section complexity. At the end, offer to
Ask which points should be kept, removed, or combined. Request brief justifications to help learn priorities for the next sections. Ask which points should be kept, removed, or combined. Request brief justifications to help learn priorities for the next sections.
Provide examples: Provide examples:
- "Keep 1,4,7,9" - "Keep 1,4,7,9"
- "Remove 3 (duplicates 1)" - "Remove 3 (duplicates 1)"
- "Remove 6 (audience already knows this)" - "Remove 6 (audience already knows this)"
@@ -205,6 +213,7 @@ Provide a note: Instead of editing the doc directly, ask them to indicate what t
### Step 6: Iterative Refinement ### Step 6: Iterative Refinement
As user provides feedback: As user provides feedback:
- Use `str_replace` to make edits (never reprint the whole doc) - Use `str_replace` to make edits (never reprint the whole doc)
- **If using artifacts:** Provide link to artifact after each edit - **If using artifacts:** Provide link to artifact after each edit
- **If using files:** Just confirm edits are complete - **If using files:** Just confirm edits are complete
@@ -223,6 +232,7 @@ When section is done, confirm [SECTION NAME] is complete. Ask if ready to move t
### Near Completion ### Near Completion
As approaching completion (80%+ of sections done), announce intention to re-read the entire document and check for: As approaching completion (80%+ of sections done), announce intention to re-read the entire document and check for:
- Flow and consistency across sections - Flow and consistency across sections
- Redundancy or contradictions - Redundancy or contradictions
- Anything that feels like "slop" or generic filler - Anything that feels like "slop" or generic filler
@@ -300,11 +310,13 @@ Generate 5-10 questions that readers would realistically ask.
### Step 2: Setup Testing ### Step 2: Setup Testing
Provide testing instructions: Provide testing instructions:
1. Open a fresh Claude conversation: https://claude.ai 1. Open a fresh Claude conversation: https://claude.ai
2. Paste or share the document content (if using a shared doc platform with connectors enabled, provide the link) 2. Paste or share the document content (if using a shared doc platform with connectors enabled, provide the link)
3. Ask Reader Claude the generated questions 3. Ask Reader Claude the generated questions
For each question, instruct Reader Claude to provide: For each question, instruct Reader Claude to provide:
- The answer - The answer
- Whether anything was ambiguous or unclear - Whether anything was ambiguous or unclear
- What knowledge/context the doc assumes is already known - What knowledge/context the doc assumes is already known
@@ -314,6 +326,7 @@ Check if Reader Claude gives correct answers or misinterprets anything.
### Step 3: Additional Checks ### Step 3: Additional Checks
Also ask Reader Claude: Also ask Reader Claude:
- "What in this doc might be ambiguous or unclear to readers?" - "What in this doc might be ambiguous or unclear to readers?"
- "What knowledge or context does this doc assume readers already have?" - "What knowledge or context does this doc assume readers already have?"
- "Are there any internal contradictions or inconsistencies?" - "Are there any internal contradictions or inconsistencies?"
@@ -343,6 +356,7 @@ Ask if they want one more review, or if the work is done.
**If user wants final review, provide it. Otherwise:** **If user wants final review, provide it. Otherwise:**
Announce document completion. Provide a few final tips: Announce document completion. Provide a few final tips:
- Consider linking this conversation in an appendix so readers can see how the doc was developed - Consider linking this conversation in an appendix so readers can see how the doc was developed
- Use appendices to provide depth without bloating the main doc - Use appendices to provide depth without bloating the main doc
- Update the doc as feedback is received from real readers - Update the doc as feedback is received from real readers
@@ -350,26 +364,31 @@ Announce document completion. Provide a few final tips:
## Tips for Effective Guidance ## Tips for Effective Guidance
**Tone:** **Tone:**
- Be direct and procedural - Be direct and procedural
- Explain rationale briefly when it affects user behavior - Explain rationale briefly when it affects user behavior
- Don't try to "sell" the approach - just execute it - Don't try to "sell" the approach - just execute it
**Handling Deviations:** **Handling Deviations:**
- If user wants to skip a stage: Ask if they want to skip this and write freeform - If user wants to skip a stage: Ask if they want to skip this and write freeform
- If user seems frustrated: Acknowledge this is taking longer than expected. Suggest ways to move faster - If user seems frustrated: Acknowledge this is taking longer than expected. Suggest ways to move faster
- Always give user agency to adjust the process - Always give user agency to adjust the process
**Context Management:** **Context Management:**
- Throughout, if context is missing on something mentioned, proactively ask - Throughout, if context is missing on something mentioned, proactively ask
- Don't let gaps accumulate - address them as they come up - Don't let gaps accumulate - address them as they come up
**Artifact Management:** **Artifact Management:**
- Use `create_file` for drafting full sections - Use `create_file` for drafting full sections
- Use `str_replace` for all edits - Use `str_replace` for all edits
- Provide artifact link after every change - Provide artifact link after every change
- Never use artifacts for brainstorming lists - that's just conversation - Never use artifacts for brainstorming lists - that's just conversation
**Quality over Speed:** **Quality over Speed:**
- Don't rush through stages - Don't rush through stages
- Each iteration should make meaningful improvements - Each iteration should make meaningful improvements
- The goal is a document that actually works for readers - The goal is a document that actually works for readers
+199 -92
View File
@@ -1,6 +1,6 @@
--- ---
name: docx name: docx
description: "Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of \"Word doc\", \"word document\", \".docx\", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a \"report\", \"memo\", \"letter\", \"template\", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation." description: 'Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of "Word doc", "word document", ".docx", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a "report", "memo", "letter", "template", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.'
license: Proprietary. LICENSE.txt has complete terms license: Proprietary. LICENSE.txt has complete terms
--- ---
@@ -12,10 +12,10 @@ A .docx file is a ZIP archive containing XML files.
## Quick Reference ## Quick Reference
| Task | Approach | | Task | Approach |
|------|----------| | ---------------------- | ----------------------------------------------------------------- |
| Read/analyze content | `pandoc` or unpack for raw XML | | Read/analyze content | `pandoc` or unpack for raw XML |
| Create new document | Use `docx-js` - see Creating New Documents below | | Create new document | Use `docx-js` - see Creating New Documents below |
| Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below | | Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below |
### Converting .doc to .docx ### Converting .doc to .docx
@@ -58,18 +58,49 @@ python scripts/accept_changes.py input.docx output.docx
Generate .docx files with JavaScript, then validate. Install: `npm install -g docx` Generate .docx files with JavaScript, then validate. Install: `npm install -g docx`
### Setup ### Setup
```javascript
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
VerticalAlign, PageNumber, PageBreak } = require('docx');
const doc = new Document({ sections: [{ children: [/* content */] }] }); ```javascript
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); const {
Document,
Packer,
Paragraph,
TextRun,
Table,
TableRow,
TableCell,
ImageRun,
Header,
Footer,
AlignmentType,
PageOrientation,
LevelFormat,
ExternalHyperlink,
TableOfContents,
HeadingLevel,
BorderStyle,
WidthType,
ShadingType,
VerticalAlign,
PageNumber,
PageBreak,
} = require('docx');
const doc = new Document({
sections: [
{
children: [
/* content */
],
},
],
});
Packer.toBuffer(doc).then((buffer) => fs.writeFileSync('doc.docx', buffer));
``` ```
### Validation ### Validation
After creating the file, validate it. If validation fails, unpack, fix the XML, and repack. After creating the file, validate it. If validation fails, unpack, fix the XML, and repack.
```bash ```bash
python scripts/office/validate.py doc.docx python scripts/office/validate.py doc.docx
``` ```
@@ -79,28 +110,33 @@ python scripts/office/validate.py doc.docx
```javascript ```javascript
// CRITICAL: docx-js defaults to A4, not US Letter // CRITICAL: docx-js defaults to A4, not US Letter
// Always set page size explicitly for consistent results // Always set page size explicitly for consistent results
sections: [{ sections: [
properties: { {
page: { properties: {
size: { page: {
width: 12240, // 8.5 inches in DXA size: {
height: 15840 // 11 inches in DXA width: 12240, // 8.5 inches in DXA
height: 15840, // 11 inches in DXA
},
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // 1 inch margins
}, },
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins },
} children: [
/* content */
],
}, },
children: [/* content */] ];
}]
``` ```
**Common page sizes (DXA units, 1440 DXA = 1 inch):** **Common page sizes (DXA units, 1440 DXA = 1 inch):**
| Paper | Width | Height | Content Width (1" margins) | | Paper | Width | Height | Content Width (1" margins) |
|-------|-------|--------|---------------------------| | ------------ | ------ | ------ | -------------------------- |
| US Letter | 12,240 | 15,840 | 9,360 | | US Letter | 12,240 | 15,840 | 9,360 |
| A4 (default) | 11,906 | 16,838 | 9,026 | | A4 (default) | 11,906 | 16,838 | 9,026 |
**Landscape orientation:** docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap: **Landscape orientation:** docx-js swaps width/height internally, so pass portrait dimensions and let it handle the swap:
```javascript ```javascript
size: { size: {
width: 12240, // Pass SHORT edge as width width: 12240, // Pass SHORT edge as width
@@ -117,22 +153,36 @@ Use Arial as the default font (universally supported). Keep titles black for rea
```javascript ```javascript
const doc = new Document({ const doc = new Document({
styles: { styles: {
default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default default: { document: { run: { font: 'Arial', size: 24 } } }, // 12pt default
paragraphStyles: [ paragraphStyles: [
// IMPORTANT: Use exact IDs to override built-in styles // IMPORTANT: Use exact IDs to override built-in styles
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true, {
run: { size: 32, bold: true, font: "Arial" }, id: 'Heading1',
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel required for TOC name: 'Heading 1',
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true, basedOn: 'Normal',
run: { size: 28, bold: true, font: "Arial" }, next: 'Normal',
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } }, quickFormat: true,
] run: { size: 32, bold: true, font: 'Arial' },
paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 },
}, // outlineLevel required for TOC
{
id: 'Heading2',
name: 'Heading 2',
basedOn: 'Normal',
next: 'Normal',
quickFormat: true,
run: { size: 28, bold: true, font: 'Arial' },
paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 },
},
],
}, },
sections: [{ sections: [
children: [ {
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }), children: [
] new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun('Title')] }),
}] ],
},
],
}); });
``` ```
@@ -140,29 +190,53 @@ const doc = new Document({
```javascript ```javascript
// ❌ WRONG - never manually insert bullet characters // ❌ WRONG - never manually insert bullet characters
new Paragraph({ children: [new TextRun("• Item")] }) // BAD new Paragraph({ children: [new TextRun('• Item')] }); // BAD
new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD new Paragraph({ children: [new TextRun('\u2022 Item')] }); // BAD
// ✅ CORRECT - use numbering config with LevelFormat.BULLET // ✅ CORRECT - use numbering config with LevelFormat.BULLET
const doc = new Document({ const doc = new Document({
numbering: { numbering: {
config: [ config: [
{ reference: "bullets", {
levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT, reference: 'bullets',
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, levels: [
{ reference: "numbers", {
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT, level: 0,
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }, format: LevelFormat.BULLET,
] text: '•',
alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
},
],
},
{
reference: 'numbers',
levels: [
{
level: 0,
format: LevelFormat.DECIMAL,
text: '%1.',
alignment: AlignmentType.LEFT,
style: { paragraph: { indent: { left: 720, hanging: 360 } } },
},
],
},
],
}, },
sections: [{ sections: [
children: [ {
new Paragraph({ numbering: { reference: "bullets", level: 0 }, children: [
children: [new TextRun("Bullet item")] }), new Paragraph({
new Paragraph({ numbering: { reference: "numbers", level: 0 }, numbering: { reference: 'bullets', level: 0 },
children: [new TextRun("Numbered item")] }), children: [new TextRun('Bullet item')],
] }),
}] new Paragraph({
numbering: { reference: 'numbers', level: 0 },
children: [new TextRun('Numbered item')],
}),
],
},
],
}); });
// ⚠️ Each reference creates INDEPENDENT numbering // ⚠️ Each reference creates INDEPENDENT numbering
@@ -177,7 +251,7 @@ const doc = new Document({
```javascript ```javascript
// CRITICAL: Always set table width for consistent rendering // CRITICAL: Always set table width for consistent rendering
// CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds // CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds
const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }; const border = { style: BorderStyle.SINGLE, size: 1, color: 'CCCCCC' };
const borders = { top: border, bottom: border, left: border, right: border }; const borders = { top: border, bottom: border, left: border, right: border };
new Table({ new Table({
@@ -189,14 +263,14 @@ new Table({
new TableCell({ new TableCell({
borders, borders,
width: { size: 4680, type: WidthType.DXA }, // Also set on each cell width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID shading: { fill: 'D5E8F0', type: ShadingType.CLEAR }, // CLEAR not SOLID
margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width) margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width)
children: [new Paragraph({ children: [new TextRun("Cell")] })] children: [new Paragraph({ children: [new TextRun('Cell')] })],
}) }),
] ],
}) }),
] ],
}) });
``` ```
**Table width calculation:** **Table width calculation:**
@@ -211,6 +285,7 @@ columnWidths: [7000, 2360] // Must sum to table width
``` ```
**Width rules:** **Width rules:**
- **Always use `WidthType.DXA`** — never `WidthType.PERCENTAGE` (incompatible with Google Docs) - **Always use `WidthType.DXA`** — never `WidthType.PERCENTAGE` (incompatible with Google Docs)
- Table width must equal the sum of `columnWidths` - Table width must equal the sum of `columnWidths`
- Cell `width` must match corresponding `columnWidth` - Cell `width` must match corresponding `columnWidth`
@@ -222,49 +297,59 @@ columnWidths: [7000, 2360] // Must sum to table width
```javascript ```javascript
// CRITICAL: type parameter is REQUIRED // CRITICAL: type parameter is REQUIRED
new Paragraph({ new Paragraph({
children: [new ImageRun({ children: [
type: "png", // Required: png, jpg, jpeg, gif, bmp, svg new ImageRun({
data: fs.readFileSync("image.png"), type: 'png', // Required: png, jpg, jpeg, gif, bmp, svg
transformation: { width: 200, height: 150 }, data: fs.readFileSync('image.png'),
altText: { title: "Title", description: "Desc", name: "Name" } // All three required transformation: { width: 200, height: 150 },
})] altText: { title: 'Title', description: 'Desc', name: 'Name' }, // All three required
}) }),
],
});
``` ```
### Page Breaks ### Page Breaks
```javascript ```javascript
// CRITICAL: PageBreak must be inside a Paragraph // CRITICAL: PageBreak must be inside a Paragraph
new Paragraph({ children: [new PageBreak()] }) new Paragraph({ children: [new PageBreak()] });
// Or use pageBreakBefore // Or use pageBreakBefore
new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] }) new Paragraph({ pageBreakBefore: true, children: [new TextRun('New page')] });
``` ```
### Table of Contents ### Table of Contents
```javascript ```javascript
// CRITICAL: Headings must use HeadingLevel ONLY - no custom styles // CRITICAL: Headings must use HeadingLevel ONLY - no custom styles
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }) new TableOfContents('Table of Contents', { hyperlink: true, headingStyleRange: '1-3' });
``` ```
### Headers/Footers ### Headers/Footers
```javascript ```javascript
sections: [{ sections: [
properties: { {
page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch properties: {
page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } }, // 1440 = 1 inch
},
headers: {
default: new Header({ children: [new Paragraph({ children: [new TextRun('Header')] })] }),
},
footers: {
default: new Footer({
children: [
new Paragraph({
children: [new TextRun('Page '), new TextRun({ children: [PageNumber.CURRENT] })],
}),
],
}),
},
children: [
/* content */
],
}, },
headers: { ];
default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
},
footers: {
default: new Footer({ children: [new Paragraph({
children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
})] })
},
children: [/* content */]
}]
``` ```
### Critical Rules for docx-js ### Critical Rules for docx-js
@@ -291,9 +376,11 @@ sections: [{
**Follow all 3 steps in order.** **Follow all 3 steps in order.**
### Step 1: Unpack ### Step 1: Unpack
```bash ```bash
python scripts/office/unpack.py document.docx unpacked/ python scripts/office/unpack.py document.docx unpacked/
``` ```
Extracts XML, pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (`&#x201C;` etc.) so they survive editing. Use `--merge-runs false` to skip run merging. Extracts XML, pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (`&#x201C;` etc.) so they survive editing. Use `--merge-runs false` to skip run merging.
### Step 2: Edit XML ### Step 2: Edit XML
@@ -305,36 +392,44 @@ Edit files in `unpacked/word/`. See XML Reference below for patterns.
**Use the Edit tool directly for string replacement. Do not write Python scripts.** Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced. **Use the Edit tool directly for string replacement. Do not write Python scripts.** Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced.
**CRITICAL: Use smart quotes for new content.** When adding text with apostrophes or quotes, use XML entities to produce smart quotes: **CRITICAL: Use smart quotes for new content.** When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
```xml ```xml
<!-- Use these entities for professional typography --> <!-- Use these entities for professional typography -->
<w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t> <w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
``` ```
| Entity | Character |
|--------|-----------| | Entity | Character |
| `&#x2018;` | (left single) | | ---------- | ----------------------------- |
| `&#x2018;` | (left single) |
| `&#x2019;` | (right single / apostrophe) | | `&#x2019;` | (right single / apostrophe) |
| `&#x201C;` | “ (left double) | | `&#x201C;` | “ (left double) |
| `&#x201D;` | ” (right double) | | `&#x201D;` | ” (right double) |
**Adding comments:** Use `comment.py` to handle boilerplate across multiple XML files (text must be pre-escaped XML): **Adding comments:** Use `comment.py` to handle boilerplate across multiple XML files (text must be pre-escaped XML):
```bash ```bash
python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;" python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;"
python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0 python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0
python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name
``` ```
Then add markers to document.xml (see Comments in XML Reference). Then add markers to document.xml (see Comments in XML Reference).
### Step 3: Pack ### Step 3: Pack
```bash ```bash
python scripts/office/pack.py unpacked/ output.docx --original document.docx python scripts/office/pack.py unpacked/ output.docx --original document.docx
``` ```
Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate false` to skip. Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate false` to skip.
**Auto-repair will fix:** **Auto-repair will fix:**
- `durableId` >= 0x7FFFFFFF (regenerates valid ID) - `durableId` >= 0x7FFFFFFF (regenerates valid ID)
- Missing `xml:space="preserve"` on `<w:t>` with whitespace - Missing `xml:space="preserve"` on `<w:t>` with whitespace
**Auto-repair won't fix:** **Auto-repair won't fix:**
- Malformed XML, invalid element nesting, missing relationships, schema violations - Malformed XML, invalid element nesting, missing relationships, schema violations
### Common Pitfalls ### Common Pitfalls
@@ -355,6 +450,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
### Tracked Changes ### Tracked Changes
**Insertion:** **Insertion:**
```xml ```xml
<w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:ins w:id="1" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:t>inserted text</w:t></w:r> <w:r><w:t>inserted text</w:t></w:r>
@@ -362,6 +458,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
``` ```
**Deletion:** **Deletion:**
```xml ```xml
<w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z"> <w:del w:id="2" w:author="Claude" w:date="2025-01-01T00:00:00Z">
<w:r><w:delText>deleted text</w:delText></w:r> <w:r><w:delText>deleted text</w:delText></w:r>
@@ -371,6 +468,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
**Inside `<w:del>`**: Use `<w:delText>` instead of `<w:t>`, and `<w:delInstrText>` instead of `<w:instrText>`. **Inside `<w:del>`**: Use `<w:delText>` instead of `<w:t>`, and `<w:delInstrText>` instead of `<w:instrText>`.
**Minimal edits** - only mark what changes: **Minimal edits** - only mark what changes:
```xml ```xml
<!-- Change "30 days" to "60 days" --> <!-- Change "30 days" to "60 days" -->
<w:r><w:t>The term is </w:t></w:r> <w:r><w:t>The term is </w:t></w:r>
@@ -384,6 +482,7 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
``` ```
**Deleting entire paragraphs/list items** - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add `<w:del/>` inside `<w:pPr><w:rPr>`: **Deleting entire paragraphs/list items** - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add `<w:del/>` inside `<w:pPr><w:rPr>`:
```xml ```xml
<w:p> <w:p>
<w:pPr> <w:pPr>
@@ -397,9 +496,11 @@ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate fal
</w:del> </w:del>
</w:p> </w:p>
``` ```
Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty paragraph/list item. Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty paragraph/list item.
**Rejecting another author's insertion** - nest deletion inside their insertion: **Rejecting another author's insertion** - nest deletion inside their insertion:
```xml ```xml
<w:ins w:author="Jane" w:id="5"> <w:ins w:author="Jane" w:id="5">
<w:del w:author="Claude" w:id="10"> <w:del w:author="Claude" w:id="10">
@@ -409,6 +510,7 @@ Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty pa
``` ```
**Restoring another author's deletion** - add insertion after (don't modify their deletion): **Restoring another author's deletion** - add insertion after (don't modify their deletion):
```xml ```xml
<w:del w:author="Jane" w:id="5"> <w:del w:author="Jane" w:id="5">
<w:r><w:delText>deleted text</w:delText></w:r> <w:r><w:delText>deleted text</w:delText></w:r>
@@ -448,14 +550,19 @@ After running `comment.py` (see Step 2), add markers to document.xml. For replie
1. Add image file to `word/media/` 1. Add image file to `word/media/`
2. Add relationship to `word/_rels/document.xml.rels`: 2. Add relationship to `word/_rels/document.xml.rels`:
```xml ```xml
<Relationship Id="rId5" Type=".../image" Target="media/image1.png"/> <Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
``` ```
3. Add content type to `[Content_Types].xml`: 3. Add content type to `[Content_Types].xml`:
```xml ```xml
<Default Extension="png" ContentType="image/png"/> <Default Extension="png" ContentType="image/png"/>
``` ```
4. Reference in document.xml: 4. Reference in document.xml:
```xml ```xml
<w:drawing> <w:drawing>
<wp:inline> <wp:inline>
@@ -8,15 +8,15 @@ description: This skill provides guidance and enforcement rules for implementing
When enabling email/password authentication, configure `emailVerification.sendVerificationEmail` to verify user email addresses. This helps prevent fake sign-ups and ensures users have access to the email they registered with. When enabling email/password authentication, configure `emailVerification.sendVerificationEmail` to verify user email addresses. This helps prevent fake sign-ups and ensures users have access to the email they registered with.
```ts ```ts
import { betterAuth } from "better-auth"; import { betterAuth } from 'better-auth';
import { sendEmail } from "./email"; // your email sending function import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({ export const auth = betterAuth({
emailVerification: { emailVerification: {
sendVerificationEmail: async ({ user, url, token }, request) => { sendVerificationEmail: async ({ user, url, token }, request) => {
await sendEmail({ await sendEmail({
to: user.email, to: user.email,
subject: "Verify your email address", subject: 'Verify your email address',
text: `Click the link to verify your email: ${url}`, text: `Click the link to verify your email: ${url}`,
}); });
}, },
@@ -53,7 +53,7 @@ Always use absolute URLs (including the origin) for callback URLs in sign-up and
```ts ```ts
const { data, error } = await authClient.signUp.email({ const { data, error } = await authClient.signUp.email({
callbackURL: "https://example.com/callback", // absolute URL with origin callbackURL: 'https://example.com/callback', // absolute URL with origin
}); });
``` ```
@@ -64,8 +64,8 @@ Password reset flows are essential to any email/password system, we recommend se
To allow users to reset a password first you need to provide `sendResetPassword` function to the email and password authenticator. To allow users to reset a password first you need to provide `sendResetPassword` function to the email and password authenticator.
```ts ```ts
import { betterAuth } from "better-auth"; import { betterAuth } from 'better-auth';
import { sendEmail } from "./email"; // your email sending function import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({ export const auth = betterAuth({
emailAndPassword: { emailAndPassword: {
@@ -74,7 +74,7 @@ export const auth = betterAuth({
sendResetPassword: async ({ user, url, token }, request) => { sendResetPassword: async ({ user, url, token }, request) => {
void sendEmail({ void sendEmail({
to: user.email, to: user.email,
subject: "Reset your password", subject: 'Reset your password',
text: `Click the link to reset your password: ${url}`, text: `Click the link to reset your password: ${url}`,
}); });
}, },
@@ -148,6 +148,7 @@ The `redirectTo` parameter is validated against your `trustedOrigins` configurat
#### Password requirements #### Password requirements
During password reset, the new password must meet length requirements: During password reset, the new password must meet length requirements:
- **Minimum**: 8 characters (default), configurable via `minPasswordLength` - **Minimum**: 8 characters (default), configurable via `minPasswordLength`
- **Maximum**: 128 characters (default), configurable via `maxPasswordLength` - **Maximum**: 128 characters (default), configurable via `maxPasswordLength`
@@ -168,8 +169,8 @@ Once the password reset configurations are set-up, you can now call the `request
```ts ```ts
const data = await auth.api.requestPasswordReset({ const data = await auth.api.requestPasswordReset({
body: { body: {
email: "john.doe@example.com", // required email: 'john.doe@example.com', // required
redirectTo: "https://example.com/reset-password", redirectTo: 'https://example.com/reset-password',
}, },
}); });
``` ```
@@ -178,8 +179,8 @@ Or authClient:
```ts ```ts
const { data, error } = await authClient.requestPasswordReset({ const { data, error } = await authClient.requestPasswordReset({
email: "john.doe@example.com", // required email: 'john.doe@example.com', // required
redirectTo: "https://example.com/reset-password", redirectTo: 'https://example.com/reset-password',
}); });
``` ```
@@ -198,8 +199,8 @@ Better Auth uses `scrypt` by default for password hashing. This is a solid choic
To use a different algorithm (e.g., Argon2id), provide custom `hash` and `verify` functions in the `emailAndPassword.password` configuration: To use a different algorithm (e.g., Argon2id), provide custom `hash` and `verify` functions in the `emailAndPassword.password` configuration:
```ts ```ts
import { betterAuth } from "better-auth"; import { betterAuth } from 'better-auth';
import { hash, verify, type Options } from "@node-rs/argon2"; import { hash, verify, type Options } from '@node-rs/argon2';
const argon2Options: Options = { const argon2Options: Options = {
memoryCost: 65536, // 64 MiB memoryCost: 65536, // 64 MiB
@@ -214,8 +215,7 @@ export const auth = betterAuth({
enabled: true, enabled: true,
password: { password: {
hash: (password) => hash(password, argon2Options), hash: (password) => hash(password, argon2Options),
verify: ({ password, hash: storedHash }) => verify: ({ password, hash: storedHash }) => verify(storedHash, password, argon2Options),
verify(storedHash, password, argon2Options),
}, },
}, },
}); });
@@ -41,21 +41,25 @@ Before creating a sequence, understand:
## Core Principles ## Core Principles
### 1. One Email, One Job ### 1. One Email, One Job
- Each email has one primary purpose - Each email has one primary purpose
- One main CTA per email - One main CTA per email
- Don't try to do everything - Don't try to do everything
### 2. Value Before Ask ### 2. Value Before Ask
- Lead with usefulness - Lead with usefulness
- Build trust through content - Build trust through content
- Earn the right to sell - Earn the right to sell
### 3. Relevance Over Volume ### 3. Relevance Over Volume
- Fewer, better emails win - Fewer, better emails win
- Segment for relevance - Segment for relevance
- Quality > frequency - Quality > frequency
### 4. Clear Path Forward ### 4. Clear Path Forward
- Every email moves them somewhere - Every email moves them somewhere
- Links should do something useful - Links should do something useful
- Make next steps obvious - Make next steps obvious
@@ -65,28 +69,33 @@ Before creating a sequence, understand:
## Email Sequence Strategy ## Email Sequence Strategy
### Sequence Length ### Sequence Length
- Welcome: 3-7 emails - Welcome: 3-7 emails
- Lead nurture: 5-10 emails - Lead nurture: 5-10 emails
- Onboarding: 5-10 emails - Onboarding: 5-10 emails
- Re-engagement: 3-5 emails - Re-engagement: 3-5 emails
Depends on: Depends on:
- Sales cycle length - Sales cycle length
- Product complexity - Product complexity
- Relationship stage - Relationship stage
### Timing/Delays ### Timing/Delays
- Welcome email: Immediately - Welcome email: Immediately
- Early sequence: 1-2 days apart - Early sequence: 1-2 days apart
- Nurture: 2-4 days apart - Nurture: 2-4 days apart
- Long-term: Weekly or bi-weekly - Long-term: Weekly or bi-weekly
Consider: Consider:
- B2B: Avoid weekends - B2B: Avoid weekends
- B2C: Test weekends - B2C: Test weekends
- Time zones: Send at local time - Time zones: Send at local time
### Subject Line Strategy ### Subject Line Strategy
- Clear > Clever - Clear > Clever
- Specific > Vague - Specific > Vague
- Benefit or curiosity-driven - Benefit or curiosity-driven
@@ -94,6 +103,7 @@ Consider:
- Test emoji (they're polarizing) - Test emoji (they're polarizing)
**Patterns that work:** **Patterns that work:**
- Question: "Still struggling with X?" - Question: "Still struggling with X?"
- How-to: "How to [achieve outcome] in [timeframe]" - How-to: "How to [achieve outcome] in [timeframe]"
- Number: "3 ways to [benefit]" - Number: "3 ways to [benefit]"
@@ -101,6 +111,7 @@ Consider:
- Story tease: "The mistake I made with [topic]" - Story tease: "The mistake I made with [topic]"
### Preview Text ### Preview Text
- Extends the subject line - Extends the subject line
- ~90-140 characters - ~90-140 characters
- Don't repeat subject line - Don't repeat subject line
@@ -111,10 +122,12 @@ Consider:
## Sequence Types Overview ## Sequence Types Overview
### Welcome Sequence (Post-Signup) ### Welcome Sequence (Post-Signup)
**Length**: 5-7 emails over 12-14 days **Length**: 5-7 emails over 12-14 days
**Goal**: Activate, build trust, convert **Goal**: Activate, build trust, convert
Key emails: Key emails:
1. Welcome + deliver promised value (immediate) 1. Welcome + deliver promised value (immediate)
2. Quick win (day 1-2) 2. Quick win (day 1-2)
3. Story/Why (day 3-4) 3. Story/Why (day 3-4)
@@ -124,10 +137,12 @@ Key emails:
7. Conversion (day 12-14) 7. Conversion (day 12-14)
### Lead Nurture Sequence (Pre-Sale) ### Lead Nurture Sequence (Pre-Sale)
**Length**: 6-8 emails over 2-3 weeks **Length**: 6-8 emails over 2-3 weeks
**Goal**: Build trust, demonstrate expertise, convert **Goal**: Build trust, demonstrate expertise, convert
Key emails: Key emails:
1. Deliver lead magnet + intro (immediate) 1. Deliver lead magnet + intro (immediate)
2. Expand on topic (day 2-3) 2. Expand on topic (day 2-3)
3. Problem deep-dive (day 4-5) 3. Problem deep-dive (day 4-5)
@@ -138,22 +153,26 @@ Key emails:
8. Direct offer (day 19-21) 8. Direct offer (day 19-21)
### Re-Engagement Sequence ### Re-Engagement Sequence
**Length**: 3-4 emails over 2 weeks **Length**: 3-4 emails over 2 weeks
**Trigger**: 30-60 days of inactivity **Trigger**: 30-60 days of inactivity
**Goal**: Win back or clean list **Goal**: Win back or clean list
Key emails: Key emails:
1. Check-in (genuine concern) 1. Check-in (genuine concern)
2. Value reminder (what's new) 2. Value reminder (what's new)
3. Incentive (special offer) 3. Incentive (special offer)
4. Last chance (stay or unsubscribe) 4. Last chance (stay or unsubscribe)
### Onboarding Sequence (Product Users) ### Onboarding Sequence (Product Users)
**Length**: 5-7 emails over 14 days **Length**: 5-7 emails over 14 days
**Goal**: Activate, drive to aha moment, upgrade **Goal**: Activate, drive to aha moment, upgrade
**Note**: Coordinate with in-app onboarding—email supports, doesn't duplicate **Note**: Coordinate with in-app onboarding—email supports, doesn't duplicate
Key emails: Key emails:
1. Welcome + first step (immediate) 1. Welcome + first step (immediate)
2. Getting started help (day 1) 2. Getting started help (day 1)
3. Feature highlight (day 2-3) 3. Feature highlight (day 2-3)
@@ -169,12 +188,14 @@ Key emails:
## Email Types by Category ## Email Types by Category
### Onboarding Emails ### Onboarding Emails
- New users series - New users series
- New customers series - New customers series
- Key onboarding step reminders - Key onboarding step reminders
- New user invites - New user invites
### Retention Emails ### Retention Emails
- Upgrade to paid - Upgrade to paid
- Upgrade to higher plan - Upgrade to higher plan
- Ask for review - Ask for review
@@ -184,21 +205,25 @@ Key emails:
- Referral program - Referral program
### Billing Emails ### Billing Emails
- Switch to annual - Switch to annual
- Failed payment recovery - Failed payment recovery
- Cancellation survey - Cancellation survey
- Upcoming renewal reminders - Upcoming renewal reminders
### Usage Emails ### Usage Emails
- Daily/weekly/monthly summaries - Daily/weekly/monthly summaries
- Key event notifications - Key event notifications
- Milestone celebrations - Milestone celebrations
### Win-Back Emails ### Win-Back Emails
- Expired trials - Expired trials
- Cancelled customers - Cancelled customers
### Campaign Emails ### Campaign Emails
- Monthly roundup / newsletter - Monthly roundup / newsletter
- Seasonal promotions - Seasonal promotions
- Product updates - Product updates
@@ -212,6 +237,7 @@ Key emails:
## Email Copy Guidelines ## Email Copy Guidelines
### Structure ### Structure
1. **Hook**: First line grabs attention 1. **Hook**: First line grabs attention
2. **Context**: Why this matters to them 2. **Context**: Why this matters to them
3. **Value**: The useful content 3. **Value**: The useful content
@@ -219,6 +245,7 @@ Key emails:
5. **Sign-off**: Human, warm close 5. **Sign-off**: Human, warm close
### Formatting ### Formatting
- Short paragraphs (1-3 sentences) - Short paragraphs (1-3 sentences)
- White space between sections - White space between sections
- Bullet points for scanability - Bullet points for scanability
@@ -226,17 +253,20 @@ Key emails:
- Mobile-first (most read on phone) - Mobile-first (most read on phone)
### Tone ### Tone
- Conversational, not formal - Conversational, not formal
- First-person (I/we) and second-person (you) - First-person (I/we) and second-person (you)
- Active voice - Active voice
- Read it out loud—does it sound human? - Read it out loud—does it sound human?
### Length ### Length
- 50-125 words for transactional - 50-125 words for transactional
- 150-300 words for educational - 150-300 words for educational
- 300-500 words for story-driven - 300-500 words for story-driven
### CTA Guidelines ### CTA Guidelines
- Buttons for primary actions - Buttons for primary actions
- Links for secondary actions - Links for secondary actions
- One clear primary CTA per email - One clear primary CTA per email
@@ -249,6 +279,7 @@ Key emails:
## Output Format ## Output Format
### Sequence Overview ### Sequence Overview
``` ```
Sequence Name: [Name] Sequence Name: [Name]
Trigger: [What starts the sequence] Trigger: [What starts the sequence]
@@ -259,6 +290,7 @@ Exit Conditions: [When they leave the sequence]
``` ```
### For Each Email ### For Each Email
``` ```
Email [#]: [Name/Purpose] Email [#]: [Name/Purpose]
Send: [Timing] Send: [Timing]
@@ -270,6 +302,7 @@ Segment/Conditions: [If applicable]
``` ```
### Metrics Plan ### Metrics Plan
What to measure and benchmarks What to measure and benchmarks
--- ---
@@ -288,13 +321,13 @@ What to measure and benchmarks
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key email tools: For implementation, see the [tools registry](../../tools/REGISTRY.md). Key email tools:
| Tool | Best For | MCP | Guide | | Tool | Best For | MCP | Guide |
|------|----------|:---:|-------| | --------------- | -------------------------------- | :-: | --------------------------------------------------------- |
| **Customer.io** | Behavior-based automation | - | [customer-io.md](../../tools/integrations/customer-io.md) | | **Customer.io** | Behavior-based automation | - | [customer-io.md](../../tools/integrations/customer-io.md) |
| **Mailchimp** | SMB email marketing | ✓ | [mailchimp.md](../../tools/integrations/mailchimp.md) | | **Mailchimp** | SMB email marketing | | [mailchimp.md](../../tools/integrations/mailchimp.md) |
| **Resend** | Developer-friendly transactional | | [resend.md](../../tools/integrations/resend.md) | | **Resend** | Developer-friendly transactional | | [resend.md](../../tools/integrations/resend.md) |
| **SendGrid** | Transactional email at scale | - | [sendgrid.md](../../tools/integrations/sendgrid.md) | | **SendGrid** | Transactional email at scale | - | [sendgrid.md](../../tools/integrations/sendgrid.md) |
| **Kit** | Creator/newsletter focused | - | [kit.md](../../tools/integrations/kit.md) | | **Kit** | Creator/newsletter focused | - | [kit.md](../../tools/integrations/kit.md) |
--- ---
@@ -44,16 +44,19 @@
## Personalization ## Personalization
### Merge Fields ### Merge Fields
- First name (fallback to "there" or "friend") - First name (fallback to "there" or "friend")
- Company name (B2B) - Company name (B2B)
- Relevant data (usage, plan, etc.) - Relevant data (usage, plan, etc.)
### Dynamic Content ### Dynamic Content
- Based on segment - Based on segment
- Based on behavior - Based on behavior
- Based on stage - Based on stage
### Triggered Emails ### Triggered Emails
- Action-based sends - Action-based sends
- More relevant than time-based - More relevant than time-based
- Examples: Feature used, milestone hit, inactivity - Examples: Feature used, milestone hit, inactivity
@@ -63,16 +66,19 @@
## Segmentation Strategies ## Segmentation Strategies
### By Behavior ### By Behavior
- Openers vs. non-openers - Openers vs. non-openers
- Clickers vs. non-clickers - Clickers vs. non-clickers
- Active vs. inactive - Active vs. inactive
### By Stage ### By Stage
- Trial vs. paid - Trial vs. paid
- New vs. long-term - New vs. long-term
- Engaged vs. at-risk - Engaged vs. at-risk
### By Profile ### By Profile
- Industry/role (B2B) - Industry/role (B2B)
- Use case / goal - Use case / goal
- Company size - Company size
@@ -82,6 +88,7 @@
## Testing and Optimization ## Testing and Optimization
### What to Test ### What to Test
- Subject lines (highest impact) - Subject lines (highest impact)
- Send times - Send times
- Email length - Email length
@@ -90,12 +97,14 @@
- Sequence timing - Sequence timing
### How to Test ### How to Test
- A/B test one variable at a time - A/B test one variable at a time
- Sufficient sample size - Sufficient sample size
- Statistical significance - Statistical significance
- Document learnings - Document learnings
### Metrics to Track ### Metrics to Track
- Open rate (benchmark: 20-40%) - Open rate (benchmark: 20-40%)
- Click rate (benchmark: 2-5%) - Click rate (benchmark: 2-5%)
- Unsubscribe rate (keep under 0.5%) - Unsubscribe rate (keep under 0.5%)
@@ -5,6 +5,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Onboarding Emails ## Onboarding Emails
### New Users Series ### New Users Series
**Trigger**: User signs up (free or trial) **Trigger**: User signs up (free or trial)
**Goal**: Activate user, drive to aha moment **Goal**: Activate user, drive to aha moment
**Typical sequence**: 5-7 emails over 14 days **Typical sequence**: 5-7 emails over 14 days
@@ -22,6 +23,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### New Customers Series ### New Customers Series
**Trigger**: User converts to paid **Trigger**: User converts to paid
**Goal**: Reinforce purchase decision, drive adoption, reduce early churn **Goal**: Reinforce purchase decision, drive adoption, reduce early churn
**Typical sequence**: 3-5 emails over 14 days **Typical sequence**: 3-5 emails over 14 days
@@ -37,16 +39,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Key Onboarding Step Reminder ### Key Onboarding Step Reminder
**Trigger**: User hasn't completed critical setup step after X time **Trigger**: User hasn't completed critical setup step after X time
**Goal**: Nudge completion of high-value action **Goal**: Nudge completion of high-value action
**Format**: Single email or 2-3 email mini-sequence **Format**: Single email or 2-3 email mini-sequence
**Example triggers**: **Example triggers**:
- Hasn't connected integration after 48 hours - Hasn't connected integration after 48 hours
- Hasn't invited team member after 3 days - Hasn't invited team member after 3 days
- Hasn't completed profile after 24 hours - Hasn't completed profile after 24 hours
**Copy approach**: **Copy approach**:
- Remind them what they started - Remind them what they started
- Explain why this step matters - Explain why this step matters
- Make it easy (direct link to complete) - Make it easy (direct link to complete)
@@ -55,6 +60,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### New User Invite ### New User Invite
**Trigger**: Existing user invites teammate **Trigger**: Existing user invites teammate
**Goal**: Activate the invited user **Goal**: Activate the invited user
**Recipient**: The person being invited **Recipient**: The person being invited
@@ -64,6 +70,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
- Email 3: Final reminder (day 5) - Email 3: Final reminder (day 5)
**Copy approach**: **Copy approach**:
- Personalize with inviter's name - Personalize with inviter's name
- Explain what they're joining - Explain what they're joining
- Single CTA to accept invite - Single CTA to accept invite
@@ -74,16 +81,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Retention Emails ## Retention Emails
### Upgrade to Paid ### Upgrade to Paid
**Trigger**: Free user shows engagement, or trial ending **Trigger**: Free user shows engagement, or trial ending
**Goal**: Convert free to paid **Goal**: Convert free to paid
**Typical sequence**: 3-5 emails **Typical sequence**: 3-5 emails
**Trigger options**: **Trigger options**:
- Time-based (trial day 10, 12, 14) - Time-based (trial day 10, 12, 14)
- Behavior-based (hit usage limit, used premium feature) - Behavior-based (hit usage limit, used premium feature)
- Engagement-based (highly active free user) - Engagement-based (highly active free user)
**Sequence structure**: **Sequence structure**:
- Value summary: What they've accomplished - Value summary: What they've accomplished
- Feature comparison: What they're missing - Feature comparison: What they're missing
- Social proof: Who else upgraded - Social proof: Who else upgraded
@@ -93,17 +103,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Upgrade to Higher Plan ### Upgrade to Higher Plan
**Trigger**: User approaching plan limits or using features available on higher tier **Trigger**: User approaching plan limits or using features available on higher tier
**Goal**: Upsell to next tier **Goal**: Upsell to next tier
**Format**: Single email or 2-3 email sequence **Format**: Single email or 2-3 email sequence
**Trigger examples**: **Trigger examples**:
- 80% of seat limit reached - 80% of seat limit reached
- 90% of storage/usage limit - 90% of storage/usage limit
- Tried to use higher-tier feature - Tried to use higher-tier feature
- Power user behavior patterns - Power user behavior patterns
**Copy approach**: **Copy approach**:
- Acknowledge their growth (positive framing) - Acknowledge their growth (positive framing)
- Show what next tier unlocks - Show what next tier unlocks
- Quantify value vs. cost - Quantify value vs. cost
@@ -112,17 +125,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Ask for Review ### Ask for Review
**Trigger**: Customer milestone (30/60/90 days, key achievement, support resolution) **Trigger**: Customer milestone (30/60/90 days, key achievement, support resolution)
**Goal**: Generate social proof on G2, Capterra, app stores **Goal**: Generate social proof on G2, Capterra, app stores
**Format**: Single email **Format**: Single email
**Best timing**: **Best timing**:
- After positive support interaction - After positive support interaction
- After achieving measurable result - After achieving measurable result
- After renewal - After renewal
- NOT after billing issues or bugs - NOT after billing issues or bugs
**Copy approach**: **Copy approach**:
- Thank them for being a customer - Thank them for being a customer
- Mention specific value/milestone if possible - Mention specific value/milestone if possible
- Explain why reviews matter (help others decide) - Explain why reviews matter (help others decide)
@@ -132,17 +148,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Offer Support Proactively ### Offer Support Proactively
**Trigger**: Signs of struggle (drop in usage, failed actions, error encounters) **Trigger**: Signs of struggle (drop in usage, failed actions, error encounters)
**Goal**: Save at-risk user, improve experience **Goal**: Save at-risk user, improve experience
**Format**: Single email **Format**: Single email
**Trigger examples**: **Trigger examples**:
- Usage dropped significantly week-over-week - Usage dropped significantly week-over-week
- Multiple failed attempts at action - Multiple failed attempts at action
- Viewed help docs repeatedly - Viewed help docs repeatedly
- Stuck at same onboarding step - Stuck at same onboarding step
**Copy approach**: **Copy approach**:
- Genuine concern tone - Genuine concern tone
- Specific: "I noticed you..." (if data allows) - Specific: "I noticed you..." (if data allows)
- Offer direct help (not just link to docs) - Offer direct help (not just link to docs)
@@ -152,11 +171,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Product Usage Report ### Product Usage Report
**Trigger**: Time-based (weekly, monthly, quarterly) **Trigger**: Time-based (weekly, monthly, quarterly)
**Goal**: Demonstrate value, drive engagement, reduce churn **Goal**: Demonstrate value, drive engagement, reduce churn
**Format**: Single email, recurring **Format**: Single email, recurring
**What to include**: **What to include**:
- Key metrics/activity summary - Key metrics/activity summary
- Comparison to previous period - Comparison to previous period
- Achievements/milestones - Achievements/milestones
@@ -164,6 +185,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
- Light CTA to explore more - Light CTA to explore more
**Examples**: **Examples**:
- "You saved X hours this month" - "You saved X hours this month"
- "Your team completed X projects" - "Your team completed X projects"
- "You're in the top X% of users" - "You're in the top X% of users"
@@ -173,17 +195,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### NPS Survey ### NPS Survey
**Trigger**: Time-based (quarterly) or event-based (post-milestone) **Trigger**: Time-based (quarterly) or event-based (post-milestone)
**Goal**: Measure satisfaction, identify promoters and detractors **Goal**: Measure satisfaction, identify promoters and detractors
**Format**: Single email **Format**: Single email
**Best practices**: **Best practices**:
- Keep it simple: Just the NPS question initially - Keep it simple: Just the NPS question initially
- Follow-up form for "why" based on score - Follow-up form for "why" based on score
- Personal sender (CEO, founder, CSM) - Personal sender (CEO, founder, CSM)
- Tell them how you'll use feedback - Tell them how you'll use feedback
**Follow-up based on score**: **Follow-up based on score**:
- Promoters (9-10): Thank + ask for review/referral - Promoters (9-10): Thank + ask for review/referral
- Passives (7-8): Ask what would make it a 10 - Passives (7-8): Ask what would make it a 10
- Detractors (0-6): Personal outreach to understand issues - Detractors (0-6): Personal outreach to understand issues
@@ -191,17 +216,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Referral Program ### Referral Program
**Trigger**: Customer milestone, promoter NPS score, or campaign **Trigger**: Customer milestone, promoter NPS score, or campaign
**Goal**: Generate referrals **Goal**: Generate referrals
**Format**: Single email or periodic reminders **Format**: Single email or periodic reminders
**Good timing**: **Good timing**:
- After positive NPS response - After positive NPS response
- After customer achieves result - After customer achieves result
- After renewal - After renewal
- Seasonal campaigns - Seasonal campaigns
**Copy approach**: **Copy approach**:
- Remind them of their success - Remind them of their success
- Explain the referral offer clearly - Explain the referral offer clearly
- Make sharing easy (unique link) - Make sharing easy (unique link)
@@ -212,17 +240,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Billing Emails ## Billing Emails
### Switch to Annual ### Switch to Annual
**Trigger**: Monthly subscriber at renewal time or campaign **Trigger**: Monthly subscriber at renewal time or campaign
**Goal**: Convert monthly to annual (improve LTV, reduce churn) **Goal**: Convert monthly to annual (improve LTV, reduce churn)
**Format**: Single email or 2-email sequence **Format**: Single email or 2-email sequence
**Value proposition**: **Value proposition**:
- Calculate exact savings - Calculate exact savings
- Additional benefits (if any) - Additional benefits (if any)
- Lock in current price messaging - Lock in current price messaging
- Easy one-click switch - Easy one-click switch
**Best timing**: **Best timing**:
- Around monthly renewal date - Around monthly renewal date
- End of year / new year - End of year / new year
- After 3-6 months of loyalty - After 3-6 months of loyalty
@@ -231,17 +262,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Failed Payment Recovery ### Failed Payment Recovery
**Trigger**: Payment fails **Trigger**: Payment fails
**Goal**: Recover revenue, retain customer **Goal**: Recover revenue, retain customer
**Typical sequence**: 3-4 emails over 7-14 days **Typical sequence**: 3-4 emails over 7-14 days
**Sequence structure**: **Sequence structure**:
- Email 1 (Day 0): Friendly notice, update payment link - Email 1 (Day 0): Friendly notice, update payment link
- Email 2 (Day 3): Reminder, service may be interrupted - Email 2 (Day 3): Reminder, service may be interrupted
- Email 3 (Day 7): Urgent, account will be suspended - Email 3 (Day 7): Urgent, account will be suspended
- Email 4 (Day 10-14): Final notice, what they'll lose - Email 4 (Day 10-14): Final notice, what they'll lose
**Copy approach**: **Copy approach**:
- Assume it's an accident (card expired, etc.) - Assume it's an accident (card expired, etc.)
- Clear, direct, no guilt - Clear, direct, no guilt
- Single CTA to update payment - Single CTA to update payment
@@ -252,16 +286,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Cancellation Survey ### Cancellation Survey
**Trigger**: User cancels subscription **Trigger**: User cancels subscription
**Goal**: Learn why, opportunity to save **Goal**: Learn why, opportunity to save
**Format**: Single email (immediate) **Format**: Single email (immediate)
**Options**: **Options**:
- In-app survey at cancellation (better completion) - In-app survey at cancellation (better completion)
- Follow-up email if they skip in-app - Follow-up email if they skip in-app
- Personal outreach for high-value accounts - Personal outreach for high-value accounts
**Questions to ask**: **Questions to ask**:
- Primary reason for cancelling - Primary reason for cancelling
- What could we have done better - What could we have done better
- Would anything change your mind - Would anything change your mind
@@ -272,11 +309,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Upcoming Renewal Reminder ### Upcoming Renewal Reminder
**Trigger**: X days before renewal (14 or 30 days typical) **Trigger**: X days before renewal (14 or 30 days typical)
**Goal**: No surprise charges, opportunity to expand **Goal**: No surprise charges, opportunity to expand
**Format**: Single email **Format**: Single email
**What to include**: **What to include**:
- Renewal date and amount - Renewal date and amount
- What's included in renewal - What's included in renewal
- How to update payment/plan - How to update payment/plan
@@ -290,16 +329,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Usage Emails ## Usage Emails
### Daily/Weekly/Monthly Summary ### Daily/Weekly/Monthly Summary
**Trigger**: Time-based **Trigger**: Time-based
**Goal**: Drive engagement, demonstrate value **Goal**: Drive engagement, demonstrate value
**Format**: Single email, recurring **Format**: Single email, recurring
**Content by frequency**: **Content by frequency**:
- **Daily**: Notifications, quick stats (for high-engagement products) - **Daily**: Notifications, quick stats (for high-engagement products)
- **Weekly**: Activity summary, highlights, suggestions - **Weekly**: Activity summary, highlights, suggestions
- **Monthly**: Comprehensive report, achievements, ROI if calculable - **Monthly**: Comprehensive report, achievements, ROI if calculable
**Structure**: **Structure**:
- Key metrics at a glance - Key metrics at a glance
- Notable achievements - Notable achievements
- Activity breakdown - Activity breakdown
@@ -311,11 +353,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Key Event or Milestone Notifications ### Key Event or Milestone Notifications
**Trigger**: Specific achievement or event **Trigger**: Specific achievement or event
**Goal**: Celebrate, drive continued engagement **Goal**: Celebrate, drive continued engagement
**Format**: Single email per event **Format**: Single email per event
**Milestone examples**: **Milestone examples**:
- First [action] completed - First [action] completed
- 10th/100th [thing] created - 10th/100th [thing] created
- Goal achieved - Goal achieved
@@ -323,6 +367,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
- Usage streak - Usage streak
**Copy approach**: **Copy approach**:
- Celebration tone - Celebration tone
- Specific achievement - Specific achievement
- Context (compared to others, compared to before) - Context (compared to others, compared to before)
@@ -333,17 +378,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Win-Back Emails ## Win-Back Emails
### Expired Trials ### Expired Trials
**Trigger**: Trial ended without conversion **Trigger**: Trial ended without conversion
**Goal**: Convert or re-engage **Goal**: Convert or re-engage
**Typical sequence**: 3-4 emails over 30 days **Typical sequence**: 3-4 emails over 30 days
**Sequence structure**: **Sequence structure**:
- Email 1 (Day 1 post-expiry): Trial ended, here's what you're missing - Email 1 (Day 1 post-expiry): Trial ended, here's what you're missing
- Email 2 (Day 7): What held you back? (gather feedback) - Email 2 (Day 7): What held you back? (gather feedback)
- Email 3 (Day 14): Incentive offer (discount, extended trial) - Email 3 (Day 14): Incentive offer (discount, extended trial)
- Email 4 (Day 30): Final reach-out, door is open - Email 4 (Day 30): Final reach-out, door is open
**Segmentation**: Different approach based on trial engagement level: **Segmentation**: Different approach based on trial engagement level:
- High engagement: Focus on removing friction to convert - High engagement: Focus on removing friction to convert
- Low engagement: Offer fresh start, more onboarding help - Low engagement: Offer fresh start, more onboarding help
- No engagement: Ask what happened, offer demo/call - No engagement: Ask what happened, offer demo/call
@@ -351,16 +399,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Cancelled Customers ### Cancelled Customers
**Trigger**: Time after cancellation (30, 60, 90 days) **Trigger**: Time after cancellation (30, 60, 90 days)
**Goal**: Win back churned customers **Goal**: Win back churned customers
**Typical sequence**: 2-3 emails spread over 90 days **Typical sequence**: 2-3 emails spread over 90 days
**Sequence structure**: **Sequence structure**:
- Email 1 (Day 30): What's new since you left - Email 1 (Day 30): What's new since you left
- Email 2 (Day 60): We've addressed [common reason] - Email 2 (Day 60): We've addressed [common reason]
- Email 3 (Day 90): Special offer to return - Email 3 (Day 90): Special offer to return
**Copy approach**: **Copy approach**:
- No guilt, no desperation - No guilt, no desperation
- Genuine updates and improvements - Genuine updates and improvements
- Personalize based on cancellation reason if known - Personalize based on cancellation reason if known
@@ -373,11 +424,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
## Campaign Emails ## Campaign Emails
### Monthly Roundup / Newsletter ### Monthly Roundup / Newsletter
**Trigger**: Time-based (monthly) **Trigger**: Time-based (monthly)
**Goal**: Engagement, brand presence, content distribution **Goal**: Engagement, brand presence, content distribution
**Format**: Single email, recurring **Format**: Single email, recurring
**Content mix**: **Content mix**:
- Product updates and tips - Product updates and tips
- Customer stories - Customer stories
- Educational content - Educational content
@@ -385,6 +438,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
- Industry insights - Industry insights
**Best practices**: **Best practices**:
- Consistent send day/time - Consistent send day/time
- Scannable format - Scannable format
- Mix of content types - Mix of content types
@@ -394,11 +448,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Seasonal Promotions ### Seasonal Promotions
**Trigger**: Calendar events (Black Friday, New Year, etc.) **Trigger**: Calendar events (Black Friday, New Year, etc.)
**Goal**: Drive conversions with timely offer **Goal**: Drive conversions with timely offer
**Format**: Campaign burst (2-4 emails) **Format**: Campaign burst (2-4 emails)
**Common opportunities**: **Common opportunities**:
- New Year (fresh start, annual planning) - New Year (fresh start, annual planning)
- End of fiscal year (budget spending) - End of fiscal year (budget spending)
- Black Friday / Cyber Monday - Black Friday / Cyber Monday
@@ -406,6 +462,7 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
- Back to school / work - Back to school / work
**Sequence structure**: **Sequence structure**:
- Announcement: Offer reveal - Announcement: Offer reveal
- Reminder: Midway through promotion - Reminder: Midway through promotion
- Last chance: Final hours - Last chance: Final hours
@@ -413,17 +470,20 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Product Updates ### Product Updates
**Trigger**: New feature release **Trigger**: New feature release
**Goal**: Adoption, engagement, demonstrate momentum **Goal**: Adoption, engagement, demonstrate momentum
**Format**: Single email per major release **Format**: Single email per major release
**What to include**: **What to include**:
- What's new (clear and simple) - What's new (clear and simple)
- Why it matters (benefit, not just feature) - Why it matters (benefit, not just feature)
- How to use it (direct link) - How to use it (direct link)
- Who asked for it (community acknowledgment) - Who asked for it (community acknowledgment)
**Segmentation**: Consider targeting based on relevance: **Segmentation**: Consider targeting based on relevance:
- Users who would benefit most - Users who would benefit most
- Users who requested feature - Users who requested feature
- Power users first (for beta feel) - Power users first (for beta feel)
@@ -431,11 +491,13 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Industry News Roundup ### Industry News Roundup
**Trigger**: Time-based (weekly or monthly) **Trigger**: Time-based (weekly or monthly)
**Goal**: Thought leadership, engagement, brand value **Goal**: Thought leadership, engagement, brand value
**Format**: Curated newsletter **Format**: Curated newsletter
**Content**: **Content**:
- Curated news and links - Curated news and links
- Your take / commentary - Your take / commentary
- What it means for readers - What it means for readers
@@ -446,16 +508,19 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
--- ---
### Pricing Update ### Pricing Update
**Trigger**: Price change announcement **Trigger**: Price change announcement
**Goal**: Transparent communication, minimize churn **Goal**: Transparent communication, minimize churn
**Format**: Single email (or sequence for major changes) **Format**: Single email (or sequence for major changes)
**Timeline**: **Timeline**:
- Announce 30-60 days before change - Announce 30-60 days before change
- Reminder 14 days before - Reminder 14 days before
- Final notice 7 days before - Final notice 7 days before
**Copy approach**: **Copy approach**:
- Clear, direct, transparent - Clear, direct, transparent
- Explain the why (value delivered, costs increased) - Explain the why (value delivered, costs increased)
- Grandfather if possible (lock in old rate) - Grandfather if possible (lock in old rate)
@@ -470,12 +535,14 @@ A comprehensive guide to lifecycle and campaign emails. Use this as an audit che
Use this to audit your current email program: Use this to audit your current email program:
### Onboarding ### Onboarding
- [ ] New users series - [ ] New users series
- [ ] New customers series - [ ] New customers series
- [ ] Key onboarding step reminders - [ ] Key onboarding step reminders
- [ ] New user invite sequence - [ ] New user invite sequence
### Retention ### Retention
- [ ] Upgrade to paid sequence - [ ] Upgrade to paid sequence
- [ ] Upgrade to higher plan triggers - [ ] Upgrade to higher plan triggers
- [ ] Ask for review (timed properly) - [ ] Ask for review (timed properly)
@@ -485,21 +552,25 @@ Use this to audit your current email program:
- [ ] Referral program emails - [ ] Referral program emails
### Billing ### Billing
- [ ] Switch to annual campaign - [ ] Switch to annual campaign
- [ ] Failed payment recovery sequence - [ ] Failed payment recovery sequence
- [ ] Cancellation survey - [ ] Cancellation survey
- [ ] Upcoming renewal reminders - [ ] Upcoming renewal reminders
### Usage ### Usage
- [ ] Daily/weekly/monthly summaries - [ ] Daily/weekly/monthly summaries
- [ ] Key event notifications - [ ] Key event notifications
- [ ] Milestone celebrations - [ ] Milestone celebrations
### Win-Back ### Win-Back
- [ ] Expired trial sequence - [ ] Expired trial sequence
- [ ] Cancelled customer sequence - [ ] Cancelled customer sequence
### Campaigns ### Campaigns
- [ ] Monthly roundup / newsletter - [ ] Monthly roundup / newsletter
- [ ] Seasonal promotion calendar - [ ] Seasonal promotion calendar
- [ ] Product update announcements - [ ] Product update announcements
@@ -5,42 +5,49 @@ Detailed templates for common email sequences.
## Welcome Sequence (Post-Signup) ## Welcome Sequence (Post-Signup)
**Email 1: Welcome (Immediate)** **Email 1: Welcome (Immediate)**
- Subject: Welcome to [Product] — here's your first step - Subject: Welcome to [Product] — here's your first step
- Deliver what was promised (lead magnet, access, etc.) - Deliver what was promised (lead magnet, access, etc.)
- Single next action - Single next action
- Set expectations for future emails - Set expectations for future emails
**Email 2: Quick Win (Day 1-2)** **Email 2: Quick Win (Day 1-2)**
- Subject: Get your first [result] in 10 minutes - Subject: Get your first [result] in 10 minutes
- Enable small success - Enable small success
- Build confidence - Build confidence
- Link to helpful resource - Link to helpful resource
**Email 3: Story/Why (Day 3-4)** **Email 3: Story/Why (Day 3-4)**
- Subject: Why we built [Product] - Subject: Why we built [Product]
- Origin story or mission - Origin story or mission
- Connect emotionally - Connect emotionally
- Show you understand their problem - Show you understand their problem
**Email 4: Social Proof (Day 5-6)** **Email 4: Social Proof (Day 5-6)**
- Subject: How [Customer] achieved [Result] - Subject: How [Customer] achieved [Result]
- Case study or testimonial - Case study or testimonial
- Relatable to their situation - Relatable to their situation
- Soft CTA to explore - Soft CTA to explore
**Email 5: Overcome Objection (Day 7-8)** **Email 5: Overcome Objection (Day 7-8)**
- Subject: "I don't have time for X" — sound familiar? - Subject: "I don't have time for X" — sound familiar?
- Address common hesitation - Address common hesitation
- Reframe the obstacle - Reframe the obstacle
- Show easy path forward - Show easy path forward
**Email 6: Core Feature (Day 9-11)** **Email 6: Core Feature (Day 9-11)**
- Subject: Have you tried [Feature] yet? - Subject: Have you tried [Feature] yet?
- Highlight underused capability - Highlight underused capability
- Show clear benefit - Show clear benefit
- Direct CTA to try it - Direct CTA to try it
**Email 7: Conversion (Day 12-14)** **Email 7: Conversion (Day 12-14)**
- Subject: Ready to [upgrade/buy/commit]? - Subject: Ready to [upgrade/buy/commit]?
- Summarize value - Summarize value
- Clear offer - Clear offer
@@ -52,41 +59,49 @@ Detailed templates for common email sequences.
## Lead Nurture Sequence (Pre-Sale) ## Lead Nurture Sequence (Pre-Sale)
**Email 1: Deliver + Introduce (Immediate)** **Email 1: Deliver + Introduce (Immediate)**
- Deliver the lead magnet - Deliver the lead magnet
- Brief intro to who you are - Brief intro to who you are
- Preview what's coming - Preview what's coming
**Email 2: Expand on Topic (Day 2-3)** **Email 2: Expand on Topic (Day 2-3)**
- Related insight to lead magnet - Related insight to lead magnet
- Establish expertise - Establish expertise
- Light CTA to content - Light CTA to content
**Email 3: Problem Deep-Dive (Day 4-5)** **Email 3: Problem Deep-Dive (Day 4-5)**
- Articulate their problem deeply - Articulate their problem deeply
- Show you understand - Show you understand
- Hint at solution - Hint at solution
**Email 4: Solution Framework (Day 6-8)** **Email 4: Solution Framework (Day 6-8)**
- Your approach/methodology - Your approach/methodology
- Educational, not salesy - Educational, not salesy
- Builds toward your product - Builds toward your product
**Email 5: Case Study (Day 9-11)** **Email 5: Case Study (Day 9-11)**
- Real results from real customer - Real results from real customer
- Specific and relatable - Specific and relatable
- Soft CTA - Soft CTA
**Email 6: Differentiation (Day 12-14)** **Email 6: Differentiation (Day 12-14)**
- Why your approach is different - Why your approach is different
- Address alternatives - Address alternatives
- Build preference - Build preference
**Email 7: Objection Handler (Day 15-18)** **Email 7: Objection Handler (Day 15-18)**
- Common concern addressed - Common concern addressed
- FAQ or myth-busting - FAQ or myth-busting
- Reduce friction - Reduce friction
**Email 8: Direct Offer (Day 19-21)** **Email 8: Direct Offer (Day 19-21)**
- Clear pitch - Clear pitch
- Strong value proposition - Strong value proposition
- Specific CTA - Specific CTA
@@ -97,24 +112,28 @@ Detailed templates for common email sequences.
## Re-Engagement Sequence ## Re-Engagement Sequence
**Email 1: Check-In (Day 30-60 of inactivity)** **Email 1: Check-In (Day 30-60 of inactivity)**
- Subject: Is everything okay, [Name]? - Subject: Is everything okay, [Name]?
- Genuine concern - Genuine concern
- Ask what happened - Ask what happened
- Easy win to re-engage - Easy win to re-engage
**Email 2: Value Reminder (Day 2-3 after)** **Email 2: Value Reminder (Day 2-3 after)**
- Subject: Remember when you [achieved X]? - Subject: Remember when you [achieved X]?
- Remind of past value - Remind of past value
- What's new since they left - What's new since they left
- Quick CTA - Quick CTA
**Email 3: Incentive (Day 5-7 after)** **Email 3: Incentive (Day 5-7 after)**
- Subject: We miss you — here's something special - Subject: We miss you — here's something special
- Offer if appropriate - Offer if appropriate
- Limited time - Limited time
- Clear CTA - Clear CTA
**Email 4: Last Chance (Day 10-14 after)** **Email 4: Last Chance (Day 10-14 after)**
- Subject: Should we stop emailing you? - Subject: Should we stop emailing you?
- Honest and direct - Honest and direct
- One-click to stay or go - One-click to stay or go
@@ -127,36 +146,43 @@ Detailed templates for common email sequences.
Coordinate with in-app onboarding. Email supports, doesn't duplicate. Coordinate with in-app onboarding. Email supports, doesn't duplicate.
**Email 1: Welcome + First Step (Immediate)** **Email 1: Welcome + First Step (Immediate)**
- Confirm signup - Confirm signup
- One critical action - One critical action
- Link directly to that action - Link directly to that action
**Email 2: Getting Started Help (Day 1)** **Email 2: Getting Started Help (Day 1)**
- If they haven't completed step 1 - If they haven't completed step 1
- Quick tip or video - Quick tip or video
- Support option - Support option
**Email 3: Feature Highlight (Day 2-3)** **Email 3: Feature Highlight (Day 2-3)**
- Key feature they should know - Key feature they should know
- Specific use case - Specific use case
- In-app link - In-app link
**Email 4: Success Story (Day 4-5)** **Email 4: Success Story (Day 4-5)**
- Customer who succeeded - Customer who succeeded
- Relatable journey - Relatable journey
- Motivational - Motivational
**Email 5: Check-In (Day 7)** **Email 5: Check-In (Day 7)**
- How's it going? - How's it going?
- Ask for feedback - Ask for feedback
- Offer help - Offer help
**Email 6: Advanced Tip (Day 10-12)** **Email 6: Advanced Tip (Day 10-12)**
- Power feature - Power feature
- For engaged users - For engaged users
- Level-up content - Level-up content
**Email 7: Upgrade/Expand (Day 14+)** **Email 7: Upgrade/Expand (Day 14+)**
- For trial users: conversion push - For trial users: conversion push
- For free users: upgrade prompt - For free users: upgrade prompt
- For paid: expansion opportunity - For paid: expansion opportunity
@@ -16,28 +16,35 @@ Load plan, review critically, execute tasks in batches, report for review betwee
## The Process ## The Process
### Step 1: Load and Review Plan ### Step 1: Load and Review Plan
1. Read plan file 1. Read plan file
2. Review critically - identify any questions or concerns about the plan 2. Review critically - identify any questions or concerns about the plan
3. If concerns: Raise them with your human partner before starting 3. If concerns: Raise them with your human partner before starting
4. If no concerns: Create TodoWrite and proceed 4. If no concerns: Create TodoWrite and proceed
### Step 2: Execute Batch ### Step 2: Execute Batch
**Default: First 3 tasks** **Default: First 3 tasks**
For each task: For each task:
1. Mark as in_progress 1. Mark as in_progress
2. Follow each step exactly (plan has bite-sized steps) 2. Follow each step exactly (plan has bite-sized steps)
3. Run verifications as specified 3. Run verifications as specified
4. Mark as completed 4. Mark as completed
### Step 3: Report ### Step 3: Report
When batch complete: When batch complete:
- Show what was implemented - Show what was implemented
- Show verification output - Show verification output
- Say: "Ready for feedback." - Say: "Ready for feedback."
### Step 4: Continue ### Step 4: Continue
Based on feedback: Based on feedback:
- Apply changes if needed - Apply changes if needed
- Execute next batch - Execute next batch
- Repeat until complete - Repeat until complete
@@ -45,6 +52,7 @@ Based on feedback:
### Step 5: Complete Development ### Step 5: Complete Development
After all tasks complete and verified: After all tasks complete and verified:
- Announce: "I'm using the finishing-a-development-branch skill to complete this work." - Announce: "I'm using the finishing-a-development-branch skill to complete this work."
- **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch - **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch
- Follow that skill to verify tests, present options, execute choice - Follow that skill to verify tests, present options, execute choice
@@ -52,6 +60,7 @@ After all tasks complete and verified:
## When to Stop and Ask for Help ## When to Stop and Ask for Help
**STOP executing immediately when:** **STOP executing immediately when:**
- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear) - Hit a blocker mid-batch (missing dependency, test fails, instruction unclear)
- Plan has critical gaps preventing starting - Plan has critical gaps preventing starting
- You don't understand an instruction - You don't understand an instruction
@@ -62,12 +71,14 @@ After all tasks complete and verified:
## When to Revisit Earlier Steps ## When to Revisit Earlier Steps
**Return to Review (Step 1) when:** **Return to Review (Step 1) when:**
- Partner updates the plan based on your feedback - Partner updates the plan based on your feedback
- Fundamental approach needs rethinking - Fundamental approach needs rethinking
**Don't force through blockers** - stop and ask. **Don't force through blockers** - stop and ask.
## Remember ## Remember
- Review plan critically first - Review plan critically first
- Follow plan steps exactly - Follow plan steps exactly
- Don't skip verifications - Don't skip verifications
@@ -79,6 +90,7 @@ After all tasks complete and verified:
## Integration ## Integration
**Required workflow skills:** **Required workflow skills:**
- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting - **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting
- **superpowers:writing-plans** - Creates the plan this skill executes - **superpowers:writing-plans** - Creates the plan this skill executes
- **superpowers:finishing-a-development-branch** - Complete development after all tasks - **superpowers:finishing-a-development-branch** - Complete development after all tasks
@@ -12,6 +12,7 @@ user-invocable: true
Production-tested patterns for FastAPI with Pydantic v2, SQLAlchemy 2.0 async, and JWT authentication. Production-tested patterns for FastAPI with Pydantic v2, SQLAlchemy 2.0 async, and JWT authentication.
**Latest Versions** (verified January 2026): **Latest Versions** (verified January 2026):
- FastAPI: 0.128.0 - FastAPI: 0.128.0
- Pydantic: 2.11.7 - Pydantic: 2.11.7
- SQLAlchemy: 2.0.30 - SQLAlchemy: 2.0.30
@@ -19,6 +20,7 @@ Production-tested patterns for FastAPI with Pydantic v2, SQLAlchemy 2.0 async, a
- python-jose: 3.3.0 - python-jose: 3.3.0
**Requirements**: **Requirements**:
- Python 3.9+ (Python 3.8 support dropped in FastAPI 0.125.0) - Python 3.9+ (Python 3.8 support dropped in FastAPI 0.125.0)
- Pydantic v2.7.0+ (Pydantic v1 support completely removed in FastAPI 0.128.0) - Pydantic v2.7.0+ (Pydantic v1 support completely removed in FastAPI 0.128.0)
@@ -143,6 +145,7 @@ class ItemResponse(ItemBase):
``` ```
**Key Points**: **Key Points**:
- Use `Field()` for validation constraints - Use `Field()` for validation constraints
- Separate Create/Update/Response schemas - Separate Create/Update/Response schemas
- `from_attributes=True` enables SQLAlchemy model conversion - `from_attributes=True` enables SQLAlchemy model conversion
@@ -478,6 +481,7 @@ settings = Settings()
``` ```
Create `.env`: Create `.env`:
``` ```
DATABASE_URL=sqlite+aiosqlite:///./database.db DATABASE_URL=sqlite+aiosqlite:///./database.db
SECRET_KEY=your-super-secret-key-here SECRET_KEY=your-super-secret-key-here
@@ -517,6 +521,7 @@ This skill prevents **7** documented issues from official FastAPI GitHub and rel
**Why It Happens**: Form data parsing preloads default values and passes them to the validator, making it impossible to distinguish between fields explicitly set by the user and fields using defaults. This bug ONLY affects Form data, not JSON body data. **Why It Happens**: Form data parsing preloads default values and passes them to the validator, making it impossible to distinguish between fields explicitly set by the user and fields using defaults. This bug ONLY affects Form data, not JSON body data.
**Prevention**: **Prevention**:
```python ```python
# ✗ AVOID: Pydantic model with Form when you need field_set metadata # ✗ AVOID: Pydantic model with Form when you need field_set metadata
from typing import Annotated from typing import Annotated
@@ -547,6 +552,7 @@ async def endpoint(model: MyModel):
**Why It Happens**: When you return a custom `Response` with a `background` parameter, it overwrites all tasks added to the injected `BackgroundTasks` dependency. This is not documented and causes silent failures. **Why It Happens**: When you return a custom `Response` with a `background` parameter, it overwrites all tasks added to the injected `BackgroundTasks` dependency. This is not documented and causes silent failures.
**Prevention**: **Prevention**:
```python ```python
# ✗ WRONG: Mixing both mechanisms # ✗ WRONG: Mixing both mechanisms
from fastapi import BackgroundTasks from fastapi import BackgroundTasks
@@ -585,6 +591,7 @@ async def endpoint():
**Why It Happens**: Starting in FastAPI 0.114.0, optional form fields with `Literal` types fail validation when passed `None` via TestClient. Worked in 0.113.0. **Why It Happens**: Starting in FastAPI 0.114.0, optional form fields with `Literal` types fail validation when passed `None` via TestClient. Worked in 0.113.0.
**Prevention**: **Prevention**:
```python ```python
from typing import Annotated, Literal, Optional from typing import Annotated, Literal, Optional
from fastapi import Form from fastapi import Form
@@ -620,6 +627,7 @@ async def endpoint(attribute: Annotated[str | None, Form()] = None):
**Why It Happens**: Using Pydantic's `Json` type directly with `Form()` fails. You must accept the field as `str` and parse manually. **Why It Happens**: Using Pydantic's `Json` type directly with `Form()` fails. You must accept the field as `str` and parse manually.
**Prevention**: **Prevention**:
```python ```python
from typing import Annotated from typing import Annotated
from fastapi import Form from fastapi import Form
@@ -647,6 +655,7 @@ async def working(json_list: Annotated[str, Form()]) -> list[str]:
**Why It Happens**: When using `Annotated` with `Depends()` and a forward reference (from `__future__ import annotations`), OpenAPI schema generation fails or produces incorrect schemas. **Why It Happens**: When using `Annotated` with `Depends()` and a forward reference (from `__future__ import annotations`), OpenAPI schema generation fails or produces incorrect schemas.
**Prevention**: **Prevention**:
```python ```python
# ✗ PROBLEMATIC: Forward reference with Depends # ✗ PROBLEMATIC: Forward reference with Depends
from __future__ import annotations from __future__ import annotations
@@ -693,6 +702,7 @@ def get_potato() -> Potato: # Now works ✓
**Why It Happens**: Major breaking change when migrating from Pydantic v1 to v2. Union types with `str` in path/query parameters now always parse as `str` (worked correctly in v1). **Why It Happens**: Major breaking change when migrating from Pydantic v1 to v2. Union types with `str` in path/query parameters now always parse as `str` (worked correctly in v1).
**Prevention**: **Prevention**:
```python ```python
from uuid import UUID from uuid import UUID
@@ -734,6 +744,7 @@ class PathParams(BaseModel):
**Why It Happens**: When raising `ValueError` inside a Pydantic `@field_validator` with Form fields, FastAPI returns 500 Internal Server Error instead of the expected 422 Unprocessable Entity validation error. **Why It Happens**: When raising `ValueError` inside a Pydantic `@field_validator` with Form fields, FastAPI returns 500 Internal Server Error instead of the expected 422 Unprocessable Entity validation error.
**Prevention**: **Prevention**:
```python ```python
from typing import Annotated from typing import Annotated
from fastapi import Form from fastapi import Form
@@ -773,11 +784,13 @@ class MyForm(BaseModel):
**Cause**: Request body doesn't match Pydantic schema **Cause**: Request body doesn't match Pydantic schema
**Debug**: **Debug**:
1. Check `/docs` endpoint - test there first 1. Check `/docs` endpoint - test there first
2. Verify JSON structure matches schema 2. Verify JSON structure matches schema
3. Check required vs optional fields 3. Check required vs optional fields
**Fix**: Add custom validation error handler: **Fix**: Add custom validation error handler:
```python ```python
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@@ -795,6 +808,7 @@ async def validation_exception_handler(request, exc):
**Cause**: Missing or misconfigured CORS middleware **Cause**: Missing or misconfigured CORS middleware
**Fix**: **Fix**:
```python ```python
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@@ -810,6 +824,7 @@ app.add_middleware(
**Cause**: Blocking call in async route (e.g., `time.sleep()`, sync database client, CPU-bound operations) **Cause**: Blocking call in async route (e.g., `time.sleep()`, sync database client, CPU-bound operations)
**Symptoms** (production-scale): **Symptoms** (production-scale):
- Throughput plateaus far earlier than expected - Throughput plateaus far earlier than expected
- Latency "balloons" as concurrency increases - Latency "balloons" as concurrency increases
- Request pattern looks almost serial under load - Request pattern looks almost serial under load
@@ -817,6 +832,7 @@ app.add_middleware(
- Small scattered blocking calls that aren't obvious (not infinite loops) - Small scattered blocking calls that aren't obvious (not infinite loops)
**Fix**: Use async alternatives: **Fix**: Use async alternatives:
```python ```python
# ✗ WRONG: Blocks event loop # ✗ WRONG: Blocks event loop
import time import time
@@ -867,6 +883,7 @@ async def mixed_task():
**Cause**: Using `Optional[str]` without default **Cause**: Using `Optional[str]` without default
**Fix**: **Fix**:
```python ```python
# Wrong # Wrong
description: Optional[str] # Still required! description: Optional[str] # Still required!
@@ -915,22 +932,26 @@ Run: `uv run pytest`
## Deployment ## Deployment
### Uvicorn (Development) ### Uvicorn (Development)
```bash ```bash
uv run fastapi dev src/main.py uv run fastapi dev src/main.py
``` ```
### Uvicorn (Production) ### Uvicorn (Production)
```bash ```bash
uv run uvicorn src.main:app --host 0.0.0.0 --port 8000 uv run uvicorn src.main:app --host 0.0.0.0 --port 8000
``` ```
### Gunicorn + Uvicorn (Production with workers) ### Gunicorn + Uvicorn (Production with workers)
```bash ```bash
uv add gunicorn uv add gunicorn
uv run gunicorn src.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 uv run gunicorn src.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000
``` ```
### Docker ### Docker
```dockerfile ```dockerfile
FROM python:3.12-slim FROM python:3.12-slim
@@ -25,6 +25,7 @@ npm test / cargo test / pytest / go test ./...
``` ```
**If tests fail:** **If tests fail:**
``` ```
Tests failing (<N> failures). Must fix before completing: Tests failing (<N> failures). Must fix before completing:
@@ -114,6 +115,7 @@ Report: "Keeping branch <name>. Worktree preserved at <path>."
#### Option 4: Discard #### Option 4: Discard
**Confirm first:** **Confirm first:**
``` ```
This will permanently delete: This will permanently delete:
- Branch <name> - Branch <name>
@@ -126,6 +128,7 @@ Type 'discard' to confirm.
Wait for exact confirmation. Wait for exact confirmation.
If confirmed: If confirmed:
```bash ```bash
git checkout <base-branch> git checkout <base-branch>
git branch -D <feature-branch> git branch -D <feature-branch>
@@ -138,11 +141,13 @@ Then: Cleanup worktree (Step 5)
**For Options 1, 2, 4:** **For Options 1, 2, 4:**
Check if in worktree: Check if in worktree:
```bash ```bash
git worktree list | grep $(git branch --show-current) git worktree list | grep $(git branch --show-current)
``` ```
If yes: If yes:
```bash ```bash
git worktree remove <worktree-path> git worktree remove <worktree-path>
``` ```
@@ -151,40 +156,46 @@ git worktree remove <worktree-path>
## Quick Reference ## Quick Reference
| Option | Merge | Push | Keep Worktree | Cleanup Branch | | Option | Merge | Push | Keep Worktree | Cleanup Branch |
|--------|-------|------|---------------|----------------| | ---------------- | ----- | ---- | ------------- | -------------- |
| 1. Merge locally | ✓ | - | - | ✓ | | 1. Merge locally | ✓ | - | - | ✓ |
| 2. Create PR | - | ✓ | ✓ | - | | 2. Create PR | - | ✓ | ✓ | - |
| 3. Keep as-is | - | - | ✓ | - | | 3. Keep as-is | - | - | ✓ | - |
| 4. Discard | - | - | - | ✓ (force) | | 4. Discard | - | - | - | ✓ (force) |
## Common Mistakes ## Common Mistakes
**Skipping test verification** **Skipping test verification**
- **Problem:** Merge broken code, create failing PR - **Problem:** Merge broken code, create failing PR
- **Fix:** Always verify tests before offering options - **Fix:** Always verify tests before offering options
**Open-ended questions** **Open-ended questions**
- **Problem:** "What should I do next?" → ambiguous - **Problem:** "What should I do next?" → ambiguous
- **Fix:** Present exactly 4 structured options - **Fix:** Present exactly 4 structured options
**Automatic worktree cleanup** **Automatic worktree cleanup**
- **Problem:** Remove worktree when might need it (Option 2, 3) - **Problem:** Remove worktree when might need it (Option 2, 3)
- **Fix:** Only cleanup for Options 1 and 4 - **Fix:** Only cleanup for Options 1 and 4
**No confirmation for discard** **No confirmation for discard**
- **Problem:** Accidentally delete work - **Problem:** Accidentally delete work
- **Fix:** Require typed "discard" confirmation - **Fix:** Require typed "discard" confirmation
## Red Flags ## Red Flags
**Never:** **Never:**
- Proceed with failing tests - Proceed with failing tests
- Merge without verifying tests on result - Merge without verifying tests on result
- Delete work without confirmation - Delete work without confirmation
- Force-push without explicit request - Force-push without explicit request
**Always:** **Always:**
- Verify tests before offering options - Verify tests before offering options
- Present exactly 4 options - Present exactly 4 options
- Get typed confirmation for Option 4 - Get typed confirmation for Option 4
@@ -193,8 +204,10 @@ git worktree remove <worktree-path>
## Integration ## Integration
**Called by:** **Called by:**
- **subagent-driven-development** (Step 7) - After all tasks complete - **subagent-driven-development** (Step 7) - After all tasks complete
- **executing-plans** (Step 5) - After all batches complete - **executing-plans** (Step 5) - After all batches complete
**Pairs with:** **Pairs with:**
- **using-git-worktrees** - Cleans up worktree created by that skill - **using-git-worktrees** - Cleans up worktree created by that skill
@@ -40,22 +40,27 @@ Before providing recommendations, identify:
## Core Principles ## Core Principles
### 1. Every Field Has a Cost ### 1. Every Field Has a Cost
Each field reduces completion rate. Rule of thumb: Each field reduces completion rate. Rule of thumb:
- 3 fields: Baseline - 3 fields: Baseline
- 4-6 fields: 10-25% reduction - 4-6 fields: 10-25% reduction
- 7+ fields: 25-50%+ reduction - 7+ fields: 25-50%+ reduction
For each field, ask: For each field, ask:
- Is this absolutely necessary before we can help them? - Is this absolutely necessary before we can help them?
- Can we get this information another way? - Can we get this information another way?
- Can we ask this later? - Can we ask this later?
### 2. Value Must Exceed Effort ### 2. Value Must Exceed Effort
- Clear value proposition above form - Clear value proposition above form
- Make what they get obvious - Make what they get obvious
- Reduce perceived effort (field count, labels) - Reduce perceived effort (field count, labels)
### 3. Reduce Cognitive Load ### 3. Reduce Cognitive Load
- One question per field - One question per field
- Clear, conversational labels - Clear, conversational labels
- Logical grouping and order - Logical grouping and order
@@ -66,44 +71,52 @@ For each field, ask:
## Field-by-Field Optimization ## Field-by-Field Optimization
### Email Field ### Email Field
- Single field, no confirmation - Single field, no confirmation
- Inline validation - Inline validation
- Typo detection (did you mean gmail.com?) - Typo detection (did you mean gmail.com?)
- Proper mobile keyboard - Proper mobile keyboard
### Name Fields ### Name Fields
- Single "Name" vs. First/Last — test this - Single "Name" vs. First/Last — test this
- Single field reduces friction - Single field reduces friction
- Split needed only if personalization requires it - Split needed only if personalization requires it
### Phone Number ### Phone Number
- Make optional if possible - Make optional if possible
- If required, explain why - If required, explain why
- Auto-format as they type - Auto-format as they type
- Country code handling - Country code handling
### Company/Organization ### Company/Organization
- Auto-suggest for faster entry - Auto-suggest for faster entry
- Enrichment after submission (Clearbit, etc.) - Enrichment after submission (Clearbit, etc.)
- Consider inferring from email domain - Consider inferring from email domain
### Job Title/Role ### Job Title/Role
- Dropdown if categories matter - Dropdown if categories matter
- Free text if wide variation - Free text if wide variation
- Consider making optional - Consider making optional
### Message/Comments (Free Text) ### Message/Comments (Free Text)
- Make optional - Make optional
- Reasonable character guidance - Reasonable character guidance
- Expand on focus - Expand on focus
### Dropdown Selects ### Dropdown Selects
- "Select one..." placeholder - "Select one..." placeholder
- Searchable if many options - Searchable if many options
- Consider radio buttons if < 5 options - Consider radio buttons if < 5 options
- "Other" option with text field - "Other" option with text field
### Checkboxes (Multi-select) ### Checkboxes (Multi-select)
- Clear, parallel labels - Clear, parallel labels
- Reasonable number of options - Reasonable number of options
- Consider "Select all that apply" instruction - Consider "Select all that apply" instruction
@@ -113,34 +126,40 @@ For each field, ask:
## Form Layout Optimization ## Form Layout Optimization
### Field Order ### Field Order
1. Start with easiest fields (name, email) 1. Start with easiest fields (name, email)
2. Build commitment before asking more 2. Build commitment before asking more
3. Sensitive fields last (phone, company size) 3. Sensitive fields last (phone, company size)
4. Logical grouping if many fields 4. Logical grouping if many fields
### Labels and Placeholders ### Labels and Placeholders
- Labels: Always visible (not just placeholder) - Labels: Always visible (not just placeholder)
- Placeholders: Examples, not labels - Placeholders: Examples, not labels
- Help text: Only when genuinely helpful - Help text: Only when genuinely helpful
**Good:** **Good:**
``` ```
Email Email
[name@company.com] [name@company.com]
``` ```
**Bad:** **Bad:**
``` ```
[Enter your email address] ← Disappears on focus [Enter your email address] ← Disappears on focus
``` ```
### Visual Design ### Visual Design
- Sufficient spacing between fields - Sufficient spacing between fields
- Clear visual hierarchy - Clear visual hierarchy
- CTA button stands out - CTA button stands out
- Mobile-friendly tap targets (44px+) - Mobile-friendly tap targets (44px+)
### Single Column vs. Multi-Column ### Single Column vs. Multi-Column
- Single column: Higher completion, mobile-friendly - Single column: Higher completion, mobile-friendly
- Multi-column: Only for short related fields (First/Last name) - Multi-column: Only for short related fields (First/Last name)
- When in doubt, single column - When in doubt, single column
@@ -150,12 +169,14 @@ Email
## Multi-Step Forms ## Multi-Step Forms
### When to Use Multi-Step ### When to Use Multi-Step
- More than 5-6 fields - More than 5-6 fields
- Logically distinct sections - Logically distinct sections
- Conditional paths based on answers - Conditional paths based on answers
- Complex forms (applications, quotes) - Complex forms (applications, quotes)
### Multi-Step Best Practices ### Multi-Step Best Practices
- Progress indicator (step X of Y) - Progress indicator (step X of Y)
- Start with easy, end with sensitive - Start with easy, end with sensitive
- One topic per step - One topic per step
@@ -164,6 +185,7 @@ Email
- Clear indication of required vs. optional - Clear indication of required vs. optional
### Progressive Commitment Pattern ### Progressive Commitment Pattern
1. Low-friction start (just email) 1. Low-friction start (just email)
2. More detail (name, company) 2. More detail (name, company)
3. Qualifying questions 3. Qualifying questions
@@ -174,11 +196,13 @@ Email
## Error Handling ## Error Handling
### Inline Validation ### Inline Validation
- Validate as they move to next field - Validate as they move to next field
- Don't validate too aggressively while typing - Don't validate too aggressively while typing
- Clear visual indicators (green check, red border) - Clear visual indicators (green check, red border)
### Error Messages ### Error Messages
- Specific to the problem - Specific to the problem
- Suggest how to fix - Suggest how to fix
- Positioned near the field - Positioned near the field
@@ -188,6 +212,7 @@ Email
**Bad:** "Invalid input" **Bad:** "Invalid input"
### On Submit ### On Submit
- Focus on first error field - Focus on first error field
- Summarize errors if multiple - Summarize errors if multiple
- Preserve all entered data - Preserve all entered data
@@ -198,10 +223,12 @@ Email
## Submit Button Optimization ## Submit Button Optimization
### Button Copy ### Button Copy
Weak: "Submit" | "Send" Weak: "Submit" | "Send"
Strong: "[Action] + [What they get]" Strong: "[Action] + [What they get]"
Examples: Examples:
- "Get My Free Quote" - "Get My Free Quote"
- "Download the Guide" - "Download the Guide"
- "Request Demo" - "Request Demo"
@@ -209,12 +236,14 @@ Examples:
- "Start Free Trial" - "Start Free Trial"
### Button Placement ### Button Placement
- Immediately after last field - Immediately after last field
- Left-aligned with fields - Left-aligned with fields
- Sufficient size and contrast - Sufficient size and contrast
- Mobile: Sticky or clearly visible - Mobile: Sticky or clearly visible
### Post-Submit States ### Post-Submit States
- Loading state (disable button, show spinner) - Loading state (disable button, show spinner)
- Success confirmation (clear next steps) - Success confirmation (clear next steps)
- Error handling (clear message, focus on issue) - Error handling (clear message, focus on issue)
@@ -224,18 +253,21 @@ Examples:
## Trust and Friction Reduction ## Trust and Friction Reduction
### Near the Form ### Near the Form
- Privacy statement: "We'll never share your info" - Privacy statement: "We'll never share your info"
- Security badges if collecting sensitive data - Security badges if collecting sensitive data
- Testimonial or social proof - Testimonial or social proof
- Expected response time - Expected response time
### Reducing Perceived Effort ### Reducing Perceived Effort
- "Takes 30 seconds" - "Takes 30 seconds"
- Field count indicator - Field count indicator
- Remove visual clutter - Remove visual clutter
- Generous white space - Generous white space
### Addressing Objections ### Addressing Objections
- "No spam, unsubscribe anytime" - "No spam, unsubscribe anytime"
- "We won't share your number" - "We won't share your number"
- "No credit card required" - "No credit card required"
@@ -245,30 +277,35 @@ Examples:
## Form Types: Specific Guidance ## Form Types: Specific Guidance
### Lead Capture (Gated Content) ### Lead Capture (Gated Content)
- Minimum viable fields (often just email) - Minimum viable fields (often just email)
- Clear value proposition for what they get - Clear value proposition for what they get
- Consider asking enrichment questions post-download - Consider asking enrichment questions post-download
- Test email-only vs. email + name - Test email-only vs. email + name
### Contact Form ### Contact Form
- Essential: Email/Name + Message - Essential: Email/Name + Message
- Phone optional - Phone optional
- Set response time expectations - Set response time expectations
- Offer alternatives (chat, phone) - Offer alternatives (chat, phone)
### Demo Request ### Demo Request
- Name, Email, Company required - Name, Email, Company required
- Phone: Optional with "preferred contact" choice - Phone: Optional with "preferred contact" choice
- Use case/goal question helps personalize - Use case/goal question helps personalize
- Calendar embed can increase show rate - Calendar embed can increase show rate
### Quote/Estimate Request ### Quote/Estimate Request
- Multi-step often works well - Multi-step often works well
- Start with easy questions - Start with easy questions
- Technical details later - Technical details later
- Save progress for complex forms - Save progress for complex forms
### Survey Forms ### Survey Forms
- Progress bar essential - Progress bar essential
- One question per screen for engagement - One question per screen for engagement
- Skip logic for relevance - Skip logic for relevance
@@ -290,6 +327,7 @@ Examples:
## Measurement ## Measurement
### Key Metrics ### Key Metrics
- **Form start rate**: Page views → Started form - **Form start rate**: Page views → Started form
- **Completion rate**: Started → Submitted - **Completion rate**: Started → Submitted
- **Field drop-off**: Which fields lose people - **Field drop-off**: Which fields lose people
@@ -298,6 +336,7 @@ Examples:
- **Mobile vs. desktop**: Completion by device - **Mobile vs. desktop**: Completion by device
### What to Track ### What to Track
- Form views - Form views
- First field focus - First field focus
- Each field completion - Each field completion
@@ -310,13 +349,16 @@ Examples:
## Output Format ## Output Format
### Form Audit ### Form Audit
For each issue: For each issue:
- **Issue**: What's wrong - **Issue**: What's wrong
- **Impact**: Estimated effect on conversions - **Impact**: Estimated effect on conversions
- **Fix**: Specific recommendation - **Fix**: Specific recommendation
- **Priority**: High/Medium/Low - **Priority**: High/Medium/Low
### Recommended Form Design ### Recommended Form Design
- **Required fields**: Justified list - **Required fields**: Justified list
- **Optional fields**: With rationale - **Optional fields**: With rationale
- **Field order**: Recommended sequence - **Field order**: Recommended sequence
@@ -325,6 +367,7 @@ For each issue:
- **Layout**: Visual guidance - **Layout**: Visual guidance
### Test Hypotheses ### Test Hypotheses
Ideas to A/B test with expected outcomes Ideas to A/B test with expected outcomes
--- ---
@@ -334,6 +377,7 @@ Ideas to A/B test with expected outcomes
### Form Structure Experiments ### Form Structure Experiments
**Layout & Flow** **Layout & Flow**
- Single-step form vs. multi-step with progress bar - Single-step form vs. multi-step with progress bar
- 1-column vs. 2-column field layout - 1-column vs. 2-column field layout
- Form embedded on page vs. separate page - Form embedded on page vs. separate page
@@ -341,6 +385,7 @@ Ideas to A/B test with expected outcomes
- Form above fold vs. after content - Form above fold vs. after content
**Field Optimization** **Field Optimization**
- Reduce to minimum viable fields - Reduce to minimum viable fields
- Add or remove phone number field - Add or remove phone number field
- Add or remove company/organization field - Add or remove company/organization field
@@ -349,6 +394,7 @@ Ideas to A/B test with expected outcomes
- Hide fields for returning/known visitors - Hide fields for returning/known visitors
**Smart Forms** **Smart Forms**
- Add real-time validation for emails and phone numbers - Add real-time validation for emails and phone numbers
- Progressive profiling (ask more over time) - Progressive profiling (ask more over time)
- Conditional fields based on earlier answers - Conditional fields based on earlier answers
@@ -359,17 +405,20 @@ Ideas to A/B test with expected outcomes
### Copy & Design Experiments ### Copy & Design Experiments
**Labels & Microcopy** **Labels & Microcopy**
- Test field label clarity and length - Test field label clarity and length
- Placeholder text optimization - Placeholder text optimization
- Help text: show vs. hide vs. on-hover - Help text: show vs. hide vs. on-hover
- Error message tone (friendly vs. direct) - Error message tone (friendly vs. direct)
**CTAs & Buttons** **CTAs & Buttons**
- Button text variations ("Submit" vs. "Get My Quote" vs. specific action) - Button text variations ("Submit" vs. "Get My Quote" vs. specific action)
- Button color and size testing - Button color and size testing
- Button placement relative to fields - Button placement relative to fields
**Trust Elements** **Trust Elements**
- Add privacy assurance near form - Add privacy assurance near form
- Show trust badges next to submit - Show trust badges next to submit
- Add testimonial near form - Add testimonial near form
@@ -380,18 +429,21 @@ Ideas to A/B test with expected outcomes
### Form Type-Specific Experiments ### Form Type-Specific Experiments
**Demo Request Forms** **Demo Request Forms**
- Test with/without phone number requirement - Test with/without phone number requirement
- Add "preferred contact method" choice - Add "preferred contact method" choice
- Include "What's your biggest challenge?" question - Include "What's your biggest challenge?" question
- Test calendar embed vs. form submission - Test calendar embed vs. form submission
**Lead Capture Forms** **Lead Capture Forms**
- Email-only vs. email + name - Email-only vs. email + name
- Test value proposition messaging above form - Test value proposition messaging above form
- Gated vs. ungated content strategies - Gated vs. ungated content strategies
- Post-submission enrichment questions - Post-submission enrichment questions
**Contact Forms** **Contact Forms**
- Add department/topic routing dropdown - Add department/topic routing dropdown
- Test with/without message field requirement - Test with/without message field requirement
- Show alternative contact methods (chat, phone) - Show alternative contact methods (chat, phone)
@@ -26,35 +26,39 @@ Before designing a tool strategy, understand:
## Core Principles ## Core Principles
### 1. Solve a Real Problem ### 1. Solve a Real Problem
- Tool must provide genuine value - Tool must provide genuine value
- Solves a problem your audience actually has - Solves a problem your audience actually has
- Useful even without your main product - Useful even without your main product
### 2. Adjacent to Core Product ### 2. Adjacent to Core Product
- Related to what you sell - Related to what you sell
- Natural path from tool to product - Natural path from tool to product
- Educates on problem you solve - Educates on problem you solve
### 3. Simple and Focused ### 3. Simple and Focused
- Does one thing well - Does one thing well
- Low friction to use - Low friction to use
- Immediate value - Immediate value
### 4. Worth the Investment ### 4. Worth the Investment
- Lead value × expected leads > build cost + maintenance - Lead value × expected leads > build cost + maintenance
--- ---
## Tool Types Overview ## Tool Types Overview
| Type | Examples | Best For | | Type | Examples | Best For |
|------|----------|----------| | ----------- | -------------------------------- | --------------------------- |
| Calculators | ROI, savings, pricing estimators | Decisions involving numbers | | Calculators | ROI, savings, pricing estimators | Decisions involving numbers |
| Generators | Templates, policies, names | Creating something quickly | | Generators | Templates, policies, names | Creating something quickly |
| Analyzers | Website graders, SEO auditors | Evaluating existing work | | Analyzers | Website graders, SEO auditors | Evaluating existing work |
| Testers | Meta tag preview, speed tests | Checking if something works | | Testers | Meta tag preview, speed tests | Checking if something works |
| Libraries | Icon sets, templates, snippets | Reference material | | Libraries | Icon sets, templates, snippets | Reference material |
| Interactive | Tutorials, playgrounds, quizzes | Learning/understanding | | Interactive | Tutorials, playgrounds, quizzes | Learning/understanding |
**For detailed tool types and examples**: See [references/tool-types.md](references/tool-types.md) **For detailed tool types and examples**: See [references/tool-types.md](references/tool-types.md)
@@ -85,14 +89,15 @@ Before designing a tool strategy, understand:
### Gating Options ### Gating Options
| Approach | Pros | Cons | | Approach | Pros | Cons |
|----------|------|------| | ------------------ | --------------- | --------------- |
| Fully gated | Maximum capture | Lower usage | | Fully gated | Maximum capture | Lower usage |
| Partially gated | Balance of both | Common pattern | | Partially gated | Balance of both | Common pattern |
| Ungated + optional | Maximum reach | Lower capture | | Ungated + optional | Maximum reach | Lower capture |
| Ungated entirely | Pure SEO/brand | No direct leads | | Ungated entirely | Pure SEO/brand | No direct leads |
### Lead Capture Best Practices ### Lead Capture Best Practices
- Value exchange clear: "Get your full report" - Value exchange clear: "Get your full report"
- Minimal friction: Email only - Minimal friction: Email only
- Show preview of what they'll get - Show preview of what they'll get
@@ -103,12 +108,15 @@ Before designing a tool strategy, understand:
## SEO Considerations ## SEO Considerations
### Keyword Strategy ### Keyword Strategy
**Tool landing page**: "[thing] calculator", "[thing] generator", "free [tool type]" **Tool landing page**: "[thing] calculator", "[thing] generator", "free [tool type]"
**Supporting content**: "How to [use case]", "What is [concept]" **Supporting content**: "How to [use case]", "What is [concept]"
### Link Building ### Link Building
Free tools attract links because: Free tools attract links because:
- Genuinely useful (people reference them) - Genuinely useful (people reference them)
- Unique (can't link to just any page) - Unique (can't link to just any page)
- Shareable (social amplification) - Shareable (social amplification)
@@ -118,13 +126,16 @@ Free tools attract links because:
## Build vs. Buy ## Build vs. Buy
### Build Custom ### Build Custom
When: Unique concept, core to brand, high strategic value, have dev capacity When: Unique concept, core to brand, high strategic value, have dev capacity
### Use No-Code Tools ### Use No-Code Tools
Options: Outgrow, Involve.me, Typeform, Tally, Bubble, Webflow Options: Outgrow, Involve.me, Typeform, Tally, Bubble, Webflow
When: Speed to market, limited dev resources, testing concept When: Speed to market, limited dev resources, testing concept
### Embed Existing ### Embed Existing
When: Something good exists, white-label available, not core differentiator When: Something good exists, white-label available, not core differentiator
--- ---
@@ -132,11 +143,13 @@ When: Something good exists, white-label available, not core differentiator
## MVP Scope ## MVP Scope
### Minimum Viable Tool ### Minimum Viable Tool
1. Core functionality only—does the one thing, works reliably 1. Core functionality only—does the one thing, works reliably
2. Essential UX—clear input, obvious output, mobile works 2. Essential UX—clear input, obvious output, mobile works
3. Basic lead capture—email collection, leads go somewhere useful 3. Basic lead capture—email collection, leads go somewhere useful
### What to Skip Initially ### What to Skip Initially
Account creation, saving results, advanced features, perfect design, every edge case Account creation, saving results, advanced features, perfect design, every edge case
--- ---
@@ -145,16 +158,16 @@ Account creation, saving results, advanced features, perfect design, every edge
Rate each factor 1-5: Rate each factor 1-5:
| Factor | Score | | Factor | Score |
|--------|-------| | ---------------------------- | ------ |
| Search demand exists | ___ | | Search demand exists | \_\_\_ |
| Audience match to buyers | ___ | | Audience match to buyers | \_\_\_ |
| Uniqueness vs. existing | ___ | | Uniqueness vs. existing | \_\_\_ |
| Natural path to product | ___ | | Natural path to product | \_\_\_ |
| Build feasibility | ___ | | Build feasibility | \_\_\_ |
| Maintenance burden (inverse) | ___ | | Maintenance burden (inverse) | \_\_\_ |
| Link-building potential | ___ | | Link-building potential | \_\_\_ |
| Share-worthiness | ___ | | Share-worthiness | \_\_\_ |
**25+**: Strong candidate | **15-24**: Promising | **<15**: Reconsider **25+**: Strong candidate | **15-24**: Promising | **<15**: Reconsider
@@ -7,6 +7,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Decisions involving numbers, comparisons, estimates **Best for**: Decisions involving numbers, comparisons, estimates
**Examples**: **Examples**:
- ROI calculator - ROI calculator
- Savings calculator - Savings calculator
- Cost comparison tool - Cost comparison tool
@@ -17,12 +18,14 @@ Detailed guide to each type of marketing tool you can build.
- Break-even calculator - Break-even calculator
**Why they work**: **Why they work**:
- Personalized output - Personalized output
- High perceived value - High perceived value
- Share-worthy results - Share-worthy results
- Clear problem → solution - Clear problem → solution
**Implementation tips**: **Implementation tips**:
- Keep inputs simple - Keep inputs simple
- Show calculations transparently - Show calculations transparently
- Make results shareable - Make results shareable
@@ -35,6 +38,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Creating something useful quickly **Best for**: Creating something useful quickly
**Examples**: **Examples**:
- Policy generator (privacy, terms) - Policy generator (privacy, terms)
- Template generator - Template generator
- Name/tagline generator - Name/tagline generator
@@ -45,12 +49,14 @@ Detailed guide to each type of marketing tool you can build.
- Contract generator - Contract generator
**Why they work**: **Why they work**:
- Tangible output - Tangible output
- Saves time - Saves time
- Easily shared - Easily shared
- Repeat usage - Repeat usage
**Implementation tips**: **Implementation tips**:
- Output should be immediately usable - Output should be immediately usable
- Allow customization - Allow customization
- Offer download/export options - Offer download/export options
@@ -63,6 +69,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Evaluating existing work or assets **Best for**: Evaluating existing work or assets
**Examples**: **Examples**:
- Website grader - Website grader
- SEO analyzer - SEO analyzer
- Email subject tester - Email subject tester
@@ -73,12 +80,14 @@ Detailed guide to each type of marketing tool you can build.
- Code quality analyzer - Code quality analyzer
**Why they work**: **Why they work**:
- Curiosity-driven - Curiosity-driven
- Personalized insights - Personalized insights
- Creates awareness of problems - Creates awareness of problems
- Natural lead to solution - Natural lead to solution
**Implementation tips**: **Implementation tips**:
- Score or grade for gamification - Score or grade for gamification
- Benchmark against averages - Benchmark against averages
- Provide actionable recommendations - Provide actionable recommendations
@@ -91,6 +100,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Checking if something works **Best for**: Checking if something works
**Examples**: **Examples**:
- Meta tag preview - Meta tag preview
- Email rendering test - Email rendering test
- Mobile-friendly test - Mobile-friendly test
@@ -101,12 +111,14 @@ Detailed guide to each type of marketing tool you can build.
- Broken link finder - Broken link finder
**Why they work**: **Why they work**:
- Immediate utility - Immediate utility
- Bookmark-worthy - Bookmark-worthy
- Repeat usage - Repeat usage
- Professional necessity - Professional necessity
**Implementation tips**: **Implementation tips**:
- Fast results are essential - Fast results are essential
- Show pass/fail clearly - Show pass/fail clearly
- Provide fix instructions - Provide fix instructions
@@ -119,6 +131,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Reference material **Best for**: Reference material
**Examples**: **Examples**:
- Icon library - Icon library
- Template library - Template library
- Code snippet library - Code snippet library
@@ -129,12 +142,14 @@ Detailed guide to each type of marketing tool you can build.
- Font pairing tool - Font pairing tool
**Why they work**: **Why they work**:
- High SEO value - High SEO value
- Ongoing traffic - Ongoing traffic
- Establishes authority - Establishes authority
- Linkable asset - Linkable asset
**Implementation tips**: **Implementation tips**:
- Make searchable/filterable - Make searchable/filterable
- Allow easy copying/downloading - Allow easy copying/downloading
- Update regularly - Update regularly
@@ -147,6 +162,7 @@ Detailed guide to each type of marketing tool you can build.
**Best for**: Learning/understanding **Best for**: Learning/understanding
**Examples**: **Examples**:
- Interactive tutorials - Interactive tutorials
- Code playgrounds - Code playgrounds
- Visual explainers - Visual explainers
@@ -157,12 +173,14 @@ Detailed guide to each type of marketing tool you can build.
- Configurators - Configurators
**Why they work**: **Why they work**:
- Engages deeply - Engages deeply
- Demonstrates expertise - Demonstrates expertise
- Shareable - Shareable
- Memory-creating - Memory-creating
**Implementation tips**: **Implementation tips**:
- Make it hands-on - Make it hands-on
- Show immediate feedback - Show immediate feedback
- Lead to deeper resources - Lead to deeper resources
@@ -173,6 +191,7 @@ Detailed guide to each type of marketing tool you can build.
## Tool Concept Examples by Industry ## Tool Concept Examples by Industry
### SaaS Product ### SaaS Product
- Product ROI calculator - Product ROI calculator
- Competitor comparison tool - Competitor comparison tool
- Readiness assessment quiz - Readiness assessment quiz
@@ -180,6 +199,7 @@ Detailed guide to each type of marketing tool you can build.
- Feature configurator - Feature configurator
### Agency/Services ### Agency/Services
- Industry benchmark tool - Industry benchmark tool
- Project scoping calculator - Project scoping calculator
- Portfolio review tool - Portfolio review tool
@@ -187,6 +207,7 @@ Detailed guide to each type of marketing tool you can build.
- Proposal generator - Proposal generator
### E-commerce ### E-commerce
- Product finder quiz - Product finder quiz
- Comparison tool - Comparison tool
- Size/fit calculator - Size/fit calculator
@@ -194,6 +215,7 @@ Detailed guide to each type of marketing tool you can build.
- Gift finder - Gift finder
### Developer Tools ### Developer Tools
- Code snippet library - Code snippet library
- Testing/preview tool - Testing/preview tool
- Documentation generator - Documentation generator
@@ -201,6 +223,7 @@ Detailed guide to each type of marketing tool you can build.
- API playground - API playground
### Finance ### Finance
- Financial calculators - Financial calculators
- Investment comparison - Investment comparison
- Budget planner - Budget planner
@@ -11,6 +11,7 @@ The user provides frontend requirements: a component, page, application, or inte
## Design Thinking ## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction: Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it? - **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility). - **Constraints**: Technical requirements (framework, performance, accessibility).
@@ -19,6 +20,7 @@ Before coding, understand the context and commit to a BOLD aesthetic direction:
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional - Production-grade and functional
- Visually striking and memorable - Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view - Cohesive with a clear aesthetic point-of-view
@@ -27,6 +29,7 @@ Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
## Frontend Aesthetics Guidelines ## Frontend Aesthetics Guidelines
Focus on: Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
@@ -5,7 +5,9 @@ license: Complete terms in LICENSE.txt
--- ---
## When to use this skill ## When to use this skill
To write internal communications, use this skill for: To write internal communications, use this skill for:
- 3P updates (Progress, Plans, Problems) - 3P updates (Progress, Plans, Problems)
- Company newsletters - Company newsletters
- FAQ responses - FAQ responses
@@ -20,13 +22,14 @@ To write any internal communication:
1. **Identify the communication type** from the request 1. **Identify the communication type** from the request
2. **Load the appropriate guideline file** from the `examples/` directory: 2. **Load the appropriate guideline file** from the `examples/` directory:
- `examples/3p-updates.md` - For Progress/Plans/Problems team updates - `examples/3p-updates.md` - For Progress/Plans/Problems team updates
- `examples/company-newsletter.md` - For company-wide newsletters - `examples/company-newsletter.md` - For company-wide newsletters
- `examples/faq-answers.md` - For answering frequently asked questions - `examples/faq-answers.md` - For answering frequently asked questions
- `examples/general-comms.md` - For anything else that doesn't explicitly match one of the above - `examples/general-comms.md` - For anything else that doesn't explicitly match one of the above
3. **Follow the specific instructions** in that file for formatting, tone, and content gathering 3. **Follow the specific instructions** in that file for formatting, tone, and content gathering
If the communication type doesn't match any existing guideline, ask for clarification or more context about the desired format. If the communication type doesn't match any existing guideline, ask for clarification or more context about the desired format.
## Keywords ## Keywords
3P updates, company newsletter, company comms, weekly update, faqs, common questions, updates, internal comms 3P updates, company newsletter, company comms, weekly update, faqs, common questions, updates, internal comms
@@ -1,36 +1,38 @@
## Instructions ## Instructions
You are being asked to write a 3P update. 3P updates stand for "Progress, Plans, Problems." The main audience is for executives, leadership, other teammates, etc. They're meant to be very succinct and to-the-point: think something you can read in 30-60sec or less. They're also for people with some, but not a lot of context on what the team does. You are being asked to write a 3P update. 3P updates stand for "Progress, Plans, Problems." The main audience is for executives, leadership, other teammates, etc. They're meant to be very succinct and to-the-point: think something you can read in 30-60sec or less. They're also for people with some, but not a lot of context on what the team does.
3Ps can cover a team of any size, ranging all the way up to the entire company. The bigger the team, the less granular the tasks should be. For example, "mobile team" might have "shipped feature" or "fixed bugs," whereas the company might have really meaty 3Ps, like "hired 20 new people" or "closed 10 new deals." 3Ps can cover a team of any size, ranging all the way up to the entire company. The bigger the team, the less granular the tasks should be. For example, "mobile team" might have "shipped feature" or "fixed bugs," whereas the company might have really meaty 3Ps, like "hired 20 new people" or "closed 10 new deals."
They represent the work of the team across a time period, almost always one week. They include three sections: They represent the work of the team across a time period, almost always one week. They include three sections:
1) Progress: what the team has accomplished over the next time period. Focus mainly on things shipped, milestones achieved, tasks created, etc.
2) Plans: what the team plans to do over the next time period. Focus on what things are top-of-mind, really high priority, etc. for the team. 1. Progress: what the team has accomplished over the next time period. Focus mainly on things shipped, milestones achieved, tasks created, etc.
3) Problems: anything that is slowing the team down. This could be things like too few people, bugs or blockers that are preventing the team from moving forward, some deal that fell through, etc. 2. Plans: what the team plans to do over the next time period. Focus on what things are top-of-mind, really high priority, etc. for the team.
3. Problems: anything that is slowing the team down. This could be things like too few people, bugs or blockers that are preventing the team from moving forward, some deal that fell through, etc.
Before writing them, make sure that you know the team name. If it's not specified, you can ask explicitly what the team name you're writing for is. Before writing them, make sure that you know the team name. If it's not specified, you can ask explicitly what the team name you're writing for is.
## Tools Available ## Tools Available
Whenever possible, try to pull from available sources to get the information you need: Whenever possible, try to pull from available sources to get the information you need:
- Slack: posts from team members with their updates - ideally look for posts in large channels with lots of reactions - Slack: posts from team members with their updates - ideally look for posts in large channels with lots of reactions
- Google Drive: docs written from critical team members with lots of views - Google Drive: docs written from critical team members with lots of views
- Email: emails with lots of responses of lots of content that seems relevant - Email: emails with lots of responses of lots of content that seems relevant
- Calendar: non-recurring meetings that have a lot of importance, like product reviews, etc. - Calendar: non-recurring meetings that have a lot of importance, like product reviews, etc.
Try to gather as much context as you can, focusing on the things that covered the time period you're writing for: Try to gather as much context as you can, focusing on the things that covered the time period you're writing for:
- Progress: anything between a week ago and today - Progress: anything between a week ago and today
- Plans: anything from today to the next week - Plans: anything from today to the next week
- Problems: anything between a week ago and today - Problems: anything between a week ago and today
If you don't have access, you can ask the user for things they want to cover. They might also include these things to you directly, in which case you're mostly just formatting for this particular format. If you don't have access, you can ask the user for things they want to cover. They might also include these things to you directly, in which case you're mostly just formatting for this particular format.
## Workflow ## Workflow
1. **Clarify scope**: Confirm the team name and time period (usually past week for Progress/Problems, next 1. **Clarify scope**: Confirm the team name and time period (usually past week for Progress/Problems, next
week for Plans) week for Plans)
2. **Gather information**: Use available tools or ask the user directly 2. **Gather information**: Use available tools or ask the user directly
3. **Draft the update**: Follow the strict formatting guidelines 3. **Draft the update**: Follow the strict formatting guidelines
4. **Review**: Ensure it's concise (30-60 seconds to read) and data-driven 4. **Review**: Ensure it's concise (30-60 seconds to read) and data-driven
@@ -44,4 +46,4 @@ Progress: [1-3 sentences of content]
Plans: [1-3 sentences of content] Plans: [1-3 sentences of content]
Problems: [1-3 sentences of content] Problems: [1-3 sentences of content]
Each section should be no more than 1-3 sentences: clear, to the point. It should be data-driven, and generally include metrics where possible. The tone should be very matter-of-fact, not super prose-heavy. Each section should be no more than 1-3 sentences: clear, to the point. It should be data-driven, and generally include metrics where possible. The tone should be very matter-of-fact, not super prose-heavy.
@@ -1,12 +1,15 @@
## Instructions ## Instructions
You are being asked to write a company-wide newsletter update. You are meant to summarize the past week/month of a company in the form of a newsletter that the entire company will read. It should be maybe ~20-25 bullet points long. It will be sent via Slack and email, so make it consumable for that. You are being asked to write a company-wide newsletter update. You are meant to summarize the past week/month of a company in the form of a newsletter that the entire company will read. It should be maybe ~20-25 bullet points long. It will be sent via Slack and email, so make it consumable for that.
Ideally it includes the following attributes: Ideally it includes the following attributes:
- Lots of links: pulling documents from Google Drive that are very relevant, linking to prominent Slack messages in announce channels and from executives, perhgaps referencing emails that went company-wide, highlighting significant things that have happened in the company. - Lots of links: pulling documents from Google Drive that are very relevant, linking to prominent Slack messages in announce channels and from executives, perhgaps referencing emails that went company-wide, highlighting significant things that have happened in the company.
- Short and to-the-point: each bullet should probably be no longer than ~1-2 sentences - Short and to-the-point: each bullet should probably be no longer than ~1-2 sentences
- Use the "we" tense, as you are part of the company. Many of the bullets should say "we did this" or "we did that" - Use the "we" tense, as you are part of the company. Many of the bullets should say "we did this" or "we did that"
## Tools to use ## Tools to use
If you have access to the following tools, please try to use them. If not, you can also let the user know directly that their responses would be better if they gave them access. If you have access to the following tools, please try to use them. If not, you can also let the user know directly that their responses would be better if they gave them access.
- Slack: look for messages in channels with lots of people, with lots of reactions or lots of responses within the thread - Slack: look for messages in channels with lots of people, with lots of reactions or lots of responses within the thread
@@ -18,10 +21,13 @@ If you have access to the following tools, please try to use them. If not, you c
If you don't have access to any of these things, you can ask the user for things they want to cover. In this case, you'll mostly just be polishing up and fitting to this format more directly. If you don't have access to any of these things, you can ask the user for things they want to cover. In this case, you'll mostly just be polishing up and fitting to this format more directly.
## Sections ## Sections
The company is pretty big: 1000+ people. There are a variety of different teams and initiatives going on across the company. To make sure the update works well, try breaking it into sections of similar things. You might break into clusters like {product development, go to market, finance} or {recruiting, execution, vision}, or {external news, internal news} etc. Try to make sure the different areas of the company are highlighted well. The company is pretty big: 1000+ people. There are a variety of different teams and initiatives going on across the company. To make sure the update works well, try breaking it into sections of similar things. You might break into clusters like {product development, go to market, finance} or {recruiting, execution, vision}, or {external news, internal news} etc. Try to make sure the different areas of the company are highlighted well.
## Prioritization ## Prioritization
Focus on: Focus on:
- Company-wide impact (not team-specific details) - Company-wide impact (not team-specific details)
- Announcements from leadership - Announcements from leadership
- Major milestones and achievements - Major milestones and achievements
@@ -29,6 +35,7 @@ Focus on:
- External recognition or press - External recognition or press
Avoid: Avoid:
- Overly granular team updates (save those for 3Ps) - Overly granular team updates (save those for 3Ps)
- Information only relevant to small groups - Information only relevant to small groups
- Duplicate information already communicated - Duplicate information already communicated
@@ -36,30 +43,34 @@ Avoid:
## Example Formats ## Example Formats
:megaphone: Company Announcements :megaphone: Company Announcements
- Announcement 1 - Announcement 1
- Announcement 2 - Announcement 2
- Announcement 3 - Announcement 3
:dart: Progress on Priorities :dart: Progress on Priorities
- Area 1 - Area 1
- Sub-area 1 - Sub-area 1
- Sub-area 2 - Sub-area 2
- Sub-area 3 - Sub-area 3
- Area 2 - Area 2
- Sub-area 1 - Sub-area 1
- Sub-area 2 - Sub-area 2
- Sub-area 3 - Sub-area 3
- Area 3 - Area 3
- Sub-area 1 - Sub-area 1
- Sub-area 2 - Sub-area 2
- Sub-area 3 - Sub-area 3
:pillar: Leadership Updates :pillar: Leadership Updates
- Post 1 - Post 1
- Post 2 - Post 2
- Post 3 - Post 3
:thread: Social Updates :thread: Social Updates
- Update 1 - Update 1
- Update 2 - Update 2
- Update 3 - Update 3
@@ -1,4 +1,5 @@
## Instructions ## Instructions
You are an assistant for answering questions that are being asked across the company. Every week, there are lots of questions that get asked across the company, and your goal is to try to summarize what those questions are. We want our company to be well-informed and on the same page, so your job is to produce a set of frequently asked questions that our employees are asking and attempt to answer them. Your singular job is to do two things: You are an assistant for answering questions that are being asked across the company. Every week, there are lots of questions that get asked across the company, and your goal is to try to summarize what those questions are. We want our company to be well-informed and on the same page, so your job is to produce a set of frequently asked questions that our employees are asking and attempt to answer them. Your singular job is to do two things:
- Find questions that are big sources of confusion for lots of employees at the company, generally about things that affect a large portion of the employee base - Find questions that are big sources of confusion for lots of employees at the company, generally about things that affect a large portion of the employee base
@@ -6,25 +7,29 @@ You are an assistant for answering questions that are being asked across the com
Some examples of areas that may be interesting to folks: recent corporate events (fundraising, new executives, etc.), upcoming launches, hiring progress, changes to vision or focus, etc. Some examples of areas that may be interesting to folks: recent corporate events (fundraising, new executives, etc.), upcoming launches, hiring progress, changes to vision or focus, etc.
## Tools Available ## Tools Available
You should use the company's available tools, where communication and work happens. For most companies, it looks something like this: You should use the company's available tools, where communication and work happens. For most companies, it looks something like this:
- Slack: questions being asked across the company - it could be questions in response to posts with lots of responses, questions being asked with lots of reactions or thumbs up to show support, or anything else to show that a large number of employees want to ask the same things - Slack: questions being asked across the company - it could be questions in response to posts with lots of responses, questions being asked with lots of reactions or thumbs up to show support, or anything else to show that a large number of employees want to ask the same things
- Email: emails with FAQs written directly in them can be a good source as well - Email: emails with FAQs written directly in them can be a good source as well
- Documents: docs in places like Google Drive, linked on calendar events, etc. can also be a good source of FAQs, either directly added or inferred based on the contents of the doc - Documents: docs in places like Google Drive, linked on calendar events, etc. can also be a good source of FAQs, either directly added or inferred based on the contents of the doc
## Formatting ## Formatting
The formatting should be pretty basic: The formatting should be pretty basic:
- *Question*: [insert question - 1 sentence] - _Question_: [insert question - 1 sentence]
- *Answer*: [insert answer - 1-2 sentence] - _Answer_: [insert answer - 1-2 sentence]
## Guidance ## Guidance
Make sure you're being holistic in your questions. Don't focus too much on just the user in question or the team they are a part of, but try to capture the entire company. Try to be as holistic as you can in reading all the tools available, producing responses that are relevant to all at the company. Make sure you're being holistic in your questions. Don't focus too much on just the user in question or the team they are a part of, but try to capture the entire company. Try to be as holistic as you can in reading all the tools available, producing responses that are relevant to all at the company.
## Answer Guidelines ## Answer Guidelines
- Base answers on official company communications when possible - Base answers on official company communications when possible
- If information is uncertain, indicate that clearly - If information is uncertain, indicate that clearly
- Link to authoritative sources (docs, announcements, emails) - Link to authoritative sources (docs, announcements, emails)
- Keep tone professional but approachable - Keep tone professional but approachable
- Flag if a question requires executive input or official response - Flag if a question requires executive input or official response
@@ -1,16 +1,19 @@
## Instructions ## Instructions
You are being asked to write internal company communication that doesn't fit into the standard formats (3P
updates, newsletters, or FAQs).
Before proceeding: You are being asked to write internal company communication that doesn't fit into the standard formats (3P
1. Ask the user about their target audience updates, newsletters, or FAQs).
2. Understand the communication's purpose
3. Clarify the desired tone (formal, casual, urgent, informational)
4. Confirm any specific formatting requirements
Use these general principles: Before proceeding:
- Be clear and concise
- Use active voice 1. Ask the user about their target audience
- Put the most important information first 2. Understand the communication's purpose
- Include relevant links and references 3. Clarify the desired tone (formal, casual, urgent, informational)
- Match the company's communication style 4. Confirm any specific formatting requirements
Use these general principles:
- Be clear and concise
- Use active voice
- Put the most important information first
- Include relevant links and references
- Match the company's communication style
@@ -1,6 +1,6 @@
--- ---
name: kickstart name: kickstart
description: "Launch an orchestrator session for a milestone, issue, or task. Use when starting autonomous work on a milestone, orchestrating issue completion, or resuming from a handoff. Triggers on: kickstart, orchestrate, start milestone, resume orchestrator." description: 'Launch an orchestrator session for a milestone, issue, or task. Use when starting autonomous work on a milestone, orchestrating issue completion, or resuming from a handoff. Triggers on: kickstart, orchestrate, start milestone, resume orchestrator.'
--- ---
# Kickstart Orchestrator # Kickstart Orchestrator
@@ -8,6 +8,7 @@ description: "Launch an orchestrator session for a milestone, issue, or task. Us
Launch an orchestrator session with a single command. Replaces the manual boilerplate of specifying mission, quality gates, branch strategy, and tracking protocol. Launch an orchestrator session with a single command. Replaces the manual boilerplate of specifying mission, quality gates, branch strategy, and tracking protocol.
**Usage:** **Usage:**
``` ```
/kickstart — List open milestones, ask user to pick /kickstart — List open milestones, ask user to pick
/kickstart 0.0.9 — Orchestrate a milestone (by version) /kickstart 0.0.9 — Orchestrate a milestone (by version)
@@ -25,13 +26,13 @@ Launch an orchestrator session with a single command. Replaces the manual boiler
Parse the argument (if any) provided after `/kickstart`: Parse the argument (if any) provided after `/kickstart`:
| Pattern | Type | Example | | Pattern | Type | Example |
|---------|------|---------| | ------------------------------------------- | --------------- | ------------------------------------------------ |
| No argument | **interactive** | `/kickstart` | | No argument | **interactive** | `/kickstart` |
| Starts with `#` | **issue** | `/kickstart #42` | | Starts with `#` | **issue** | `/kickstart #42` |
| `resume` (literal) | **resume** | `/kickstart resume` | | `resume` (literal) | **resume** | `/kickstart resume` |
| Contains `-` with uppercase + digits at end | **task ID** | `/kickstart MS-SEC-001` | | Contains `-` with uppercase + digits at end | **task ID** | `/kickstart MS-SEC-001` |
| Anything else | **milestone** | `/kickstart 0.0.9` or `/kickstart M10-Telemetry` | | Anything else | **milestone** | `/kickstart 0.0.9` or `/kickstart M10-Telemetry` |
### 1b. If no argument (interactive mode) ### 1b. If no argument (interactive mode)
@@ -42,6 +43,7 @@ List open milestones and ask the user to choose:
``` ```
Present the results and ask: Present the results and ask:
``` ```
Which target do you want to orchestrate? Which target do you want to orchestrate?
A. [milestone 1] A. [milestone 1]
@@ -70,6 +72,7 @@ git branch -r | grep -E 'origin/(develop|main)' | head -1
``` ```
Read the project's `AGENTS.md` first (and `SOUL.md` if present). If only `CLAUDE.md` exists, use it as compatibility fallback. Scan for: Read the project's `AGENTS.md` first (and `SOUL.md` if present). If only `CLAUDE.md` exists, use it as compatibility fallback. Scan for:
- Quality gate commands (look for `pnpm`, `npm`, `pytest`, `lint`, `typecheck`, `test`) - Quality gate commands (look for `pnpm`, `npm`, `pytest`, `lint`, `typecheck`, `test`)
- Branch conventions - Branch conventions
- Task prefix conventions - Task prefix conventions
@@ -132,6 +135,7 @@ Read the issue description, labels, and any linked milestone.
### For resume target ### For resume target
Read `docs/tasks.md` and determine: Read `docs/tasks.md` and determine:
- How many tasks are `done` vs `not-started` vs `in-progress` vs `failed` - How many tasks are `done` vs `not-started` vs `in-progress` vs `failed`
- What the next unblocked task is - What the next unblocked task is
- Whether any tasks are stuck (in-progress with no agent) - Whether any tasks are stuck (in-progress with no agent)
@@ -149,11 +153,13 @@ Read ~/.config/mosaic/guides/orchestrator.md
``` ```
Also load skills relevant to the project's tech stack from project docs (`AGENTS.md`/`SOUL.md`) plus `~/.config/mosaic/STANDARDS.md`. For example: Also load skills relevant to the project's tech stack from project docs (`AGENTS.md`/`SOUL.md`) plus `~/.config/mosaic/STANDARDS.md`. For example:
- NestJS project → load `nestjs-best-practices` - NestJS project → load `nestjs-best-practices`
- Next.js project → load `next-best-practices`, `vercel-react-best-practices` - Next.js project → load `next-best-practices`, `vercel-react-best-practices`
- Python project → load `fastapi`, `python-performance-optimization` - Python project → load `fastapi`, `python-performance-optimization`
Always load these orchestrator-relevant skills: Always load these orchestrator-relevant skills:
- `verification-before-completion` — evidence-based completion claims - `verification-before-completion` — evidence-based completion claims
- `dispatching-parallel-agents` — parallel worker patterns - `dispatching-parallel-agents` — parallel worker patterns
@@ -183,6 +189,7 @@ Then populate `docs/tasks.md` with tasks derived from the issues:
- Create feature branches: `feature/<milestone-slug>` as the integration branch - Create feature branches: `feature/<milestone-slug>` as the integration branch
Commit the bootstrap: Commit the bootstrap:
```bash ```bash
git add docs/tasks.md docs/orchestrator-learnings.json git add docs/tasks.md docs/orchestrator-learnings.json
git commit -m "chore: Bootstrap orchestrator for <target>" git commit -m "chore: Bootstrap orchestrator for <target>"
@@ -192,6 +199,7 @@ git push
### 4b. Resume (existing tasks.md) ### 4b. Resume (existing tasks.md)
Read `docs/tasks.md` and validate: Read `docs/tasks.md` and validate:
- Schema matches expected format (id, status, description, issue, branch, etc.) - Schema matches expected format (id, status, description, issue, branch, etc.)
- No tasks stuck in `in-progress` without an active agent - No tasks stuck in `in-progress` without an active agent
- Dependencies are consistent (no circular deps, no done tasks blocking not-started) - Dependencies are consistent (no circular deps, no done tasks blocking not-started)
@@ -199,6 +207,7 @@ Read `docs/tasks.md` and validate:
Mark any orphaned `in-progress` tasks as `not-started` (previous agent likely lost context). Mark any orphaned `in-progress` tasks as `not-started` (previous agent likely lost context).
Report to user: Report to user:
``` ```
=== Resume Status === === Resume Status ===
Total tasks: 15 Total tasks: 15
@@ -262,12 +271,16 @@ When spawning a worker, provide this structure:
**Base:** {develop|main} **Base:** {develop|main}
### Requirements ### Requirements
{Issue description and acceptance criteria} {Issue description and acceptance criteria}
### Quality Gates (MANDATORY — zero tolerance) ### Quality Gates (MANDATORY — zero tolerance)
Run ALL of these before reporting success. Fix every failure. Run ALL of these before reporting success. Fix every failure.
``` ```
{quality gate commands from project} {quality gate commands from project}
``` ```
**Linting is NON-NEGOTIABLE.** Run the project linter and fix ALL violations **Linting is NON-NEGOTIABLE.** Run the project linter and fix ALL violations
in every file you touched. Do NOT leave lint warnings, do NOT disable rules, in every file you touched. Do NOT leave lint warnings, do NOT disable rules,
@@ -314,17 +327,18 @@ Then STOP COMPLETELY. Do not continue working.
If project docs (`AGENTS.md`/`SOUL.md`/`CLAUDE.md`) don't specify quality gates, check for these patterns: If project docs (`AGENTS.md`/`SOUL.md`/`CLAUDE.md`) don't specify quality gates, check for these patterns:
| File | Likely Quality Gates | | File | Likely Quality Gates |
|------|---------------------| | --------------------------------------- | --------------------------------- |
| `package.json` with `scripts.lint` | `pnpm lint` or `npm run lint` | | `package.json` with `scripts.lint` | `pnpm lint` or `npm run lint` |
| `package.json` with `scripts.test` | `pnpm test` or `npm run test` | | `package.json` with `scripts.test` | `pnpm test` or `npm run test` |
| `package.json` with `scripts.typecheck` | `pnpm typecheck` | | `package.json` with `scripts.typecheck` | `pnpm typecheck` |
| `tsconfig.json` | `pnpm tsc --noEmit` | | `tsconfig.json` | `pnpm tsc --noEmit` |
| `pyproject.toml` | `pytest`, `ruff check`, `mypy` | | `pyproject.toml` | `pytest`, `ruff check`, `mypy` |
| `.woodpecker.yml` | Parse pipeline steps for commands | | `.woodpecker.yml` | Parse pipeline steps for commands |
| `Makefile` | `make lint`, `make test` | | `Makefile` | `make lint`, `make test` |
If no quality gates can be detected, ask the user: If no quality gates can be detected, ask the user:
``` ```
I couldn't detect quality gate commands for this project. I couldn't detect quality gate commands for this project.
What commands should workers run to verify their changes? What commands should workers run to verify their changes?
@@ -20,6 +20,7 @@ If `.mosaic/product-marketing-context.md` exists, read it before asking question
The best companies don't just launch once—they launch again and again. Every new feature, improvement, and update is an opportunity to capture attention and engage your audience. The best companies don't just launch once—they launch again and again. Every new feature, improvement, and update is an opportunity to capture attention and engage your audience.
A strong launch isn't about a single moment. It's about: A strong launch isn't about a single moment. It's about:
- Getting your product into users' hands early - Getting your product into users' hands early
- Learning from real feedback - Learning from real feedback
- Making a splash at every stage - Making a splash at every stage
@@ -32,9 +33,11 @@ A strong launch isn't about a single moment. It's about:
Structure your launch marketing across three channel types. Everything should ultimately lead back to owned channels. Structure your launch marketing across three channel types. Everything should ultimately lead back to owned channels.
### Owned Channels ### Owned Channels
You own the channel (though not the audience). Direct access without algorithms or platform rules. You own the channel (though not the audience). Direct access without algorithms or platform rules.
**Examples:** **Examples:**
- Email list - Email list
- Blog - Blog
- Podcast - Podcast
@@ -42,12 +45,14 @@ You own the channel (though not the audience). Direct access without algorithms
- Website/product - Website/product
**Why they matter:** **Why they matter:**
- Get more effective over time - Get more effective over time
- No algorithm changes or pay-to-play - No algorithm changes or pay-to-play
- Direct relationship with audience - Direct relationship with audience
- Compound value from content - Compound value from content
**Start with 1-2 based on audience:** **Start with 1-2 based on audience:**
- Industry lacks quality content → Start a blog - Industry lacks quality content → Start a blog
- People want direct updates → Focus on email - People want direct updates → Focus on email
- Engagement matters → Build a community - Engagement matters → Build a community
@@ -56,15 +61,18 @@ You own the channel (though not the audience). Direct access without algorithms
Built demand through an invite-only waitlist and one-on-one onboarding sessions. Every new user got a 30-minute live demo. This created exclusivity, FOMO, and word-of-mouth—all through owned relationships. Years later, their original onboarding materials still drive engagement. Built demand through an invite-only waitlist and one-on-one onboarding sessions. Every new user got a 30-minute live demo. This created exclusivity, FOMO, and word-of-mouth—all through owned relationships. Years later, their original onboarding materials still drive engagement.
### Rented Channels ### Rented Channels
Platforms that provide visibility but you don't control. Algorithms shift, rules change, pay-to-play increases. Platforms that provide visibility but you don't control. Algorithms shift, rules change, pay-to-play increases.
**Examples:** **Examples:**
- Social media (Twitter/X, LinkedIn, Instagram) - Social media (Twitter/X, LinkedIn, Instagram)
- App stores and marketplaces - App stores and marketplaces
- YouTube - YouTube
- Reddit - Reddit
**How to use correctly:** **How to use correctly:**
- Pick 1-2 platforms where your audience is active - Pick 1-2 platforms where your audience is active
- Use them to drive traffic to owned channels - Use them to drive traffic to owned channels
- Don't rely on them as your only strategy - Don't rely on them as your only strategy
@@ -73,6 +81,7 @@ Platforms that provide visibility but you don't control. Algorithms shift, rules
Hacked virality through Twitter, YouTube, and Reddit where productivity enthusiasts were active. Encouraged community to share templates and workflows. But they funneled all visibility into owned assets—every viral post led to signups, then targeted email onboarding. Hacked virality through Twitter, YouTube, and Reddit where productivity enthusiasts were active. Encouraged community to share templates and workflows. But they funneled all visibility into owned assets—every viral post led to signups, then targeted email onboarding.
**Platform-specific tactics:** **Platform-specific tactics:**
- Twitter/X: Threads that spark conversation → link to newsletter - Twitter/X: Threads that spark conversation → link to newsletter
- LinkedIn: High-value posts → lead to gated content or email signup - LinkedIn: High-value posts → lead to gated content or email signup
- Marketplaces (Shopify, Slack): Optimize listing → drive to site for more - Marketplaces (Shopify, Slack): Optimize listing → drive to site for more
@@ -80,15 +89,18 @@ Hacked virality through Twitter, YouTube, and Reddit where productivity enthusia
Rented channels give speed, not stability. Capture momentum by bringing users into your owned ecosystem. Rented channels give speed, not stability. Capture momentum by bringing users into your owned ecosystem.
### Borrowed Channels ### Borrowed Channels
Tap into someone else's audience to shortcut the hardest part—getting noticed. Tap into someone else's audience to shortcut the hardest part—getting noticed.
**Examples:** **Examples:**
- Guest content (blog posts, podcast interviews, newsletter features) - Guest content (blog posts, podcast interviews, newsletter features)
- Collaborations (webinars, co-marketing, social takeovers) - Collaborations (webinars, co-marketing, social takeovers)
- Speaking engagements (conferences, panels, virtual summits) - Speaking engagements (conferences, panels, virtual summits)
- Influencer partnerships - Influencer partnerships
**Be proactive, not passive:** **Be proactive, not passive:**
1. List industry leaders your audience follows 1. List industry leaders your audience follows
2. Pitch win-win collaborations 2. Pitch win-win collaborations
3. Use tools like SparkToro or Listen Notes to find audience overlap 3. Use tools like SparkToro or Listen Notes to find audience overlap
@@ -106,9 +118,11 @@ Borrowed channels give instant credibility, but only work if you convert borrowe
Launching isn't a one-day event. It's a phased process that builds momentum. Launching isn't a one-day event. It's a phased process that builds momentum.
### Phase 1: Internal Launch ### Phase 1: Internal Launch
Gather initial feedback and iron out major issues before going public. Gather initial feedback and iron out major issues before going public.
**Actions:** **Actions:**
- Recruit early users one-on-one to test for free - Recruit early users one-on-one to test for free
- Collect feedback on usability gaps and missing features - Collect feedback on usability gaps and missing features
- Ensure prototype is functional enough to demo (doesn't need to be production-ready) - Ensure prototype is functional enough to demo (doesn't need to be production-ready)
@@ -116,9 +130,11 @@ Gather initial feedback and iron out major issues before going public.
**Goal:** Validate core functionality with friendly users. **Goal:** Validate core functionality with friendly users.
### Phase 2: Alpha Launch ### Phase 2: Alpha Launch
Put the product in front of external users in a controlled way. Put the product in front of external users in a controlled way.
**Actions:** **Actions:**
- Create landing page with early access signup form - Create landing page with early access signup form
- Announce the product exists - Announce the product exists
- Invite users individually to start testing - Invite users individually to start testing
@@ -127,14 +143,17 @@ Put the product in front of external users in a controlled way.
**Goal:** First external validation and initial waitlist building. **Goal:** First external validation and initial waitlist building.
### Phase 3: Beta Launch ### Phase 3: Beta Launch
Scale up early access while generating external buzz. Scale up early access while generating external buzz.
**Actions:** **Actions:**
- Work through early access list (some free, some paid) - Work through early access list (some free, some paid)
- Start marketing with teasers about problems you solve - Start marketing with teasers about problems you solve
- Recruit friends, investors, and influencers to test and share - Recruit friends, investors, and influencers to test and share
**Consider adding:** **Consider adding:**
- Coming soon landing page or waitlist - Coming soon landing page or waitlist
- "Beta" sticker in dashboard navigation - "Beta" sticker in dashboard navigation
- Email invites to early access list - Email invites to early access list
@@ -143,29 +162,35 @@ Scale up early access while generating external buzz.
**Goal:** Build buzz and refine product with broader feedback. **Goal:** Build buzz and refine product with broader feedback.
### Phase 4: Early Access Launch ### Phase 4: Early Access Launch
Shift from small-scale testing to controlled expansion. Shift from small-scale testing to controlled expansion.
**Actions:** **Actions:**
- Leak product details: screenshots, feature GIFs, demos - Leak product details: screenshots, feature GIFs, demos
- Gather quantitative usage data and qualitative feedback - Gather quantitative usage data and qualitative feedback
- Run user research with engaged users (incentivize with credits) - Run user research with engaged users (incentivize with credits)
- Optionally run product/market fit survey to refine messaging - Optionally run product/market fit survey to refine messaging
**Expansion options:** **Expansion options:**
- Option A: Throttle invites in batches (5-10% at a time) - Option A: Throttle invites in batches (5-10% at a time)
- Option B: Invite all users at once under "early access" framing - Option B: Invite all users at once under "early access" framing
**Goal:** Validate at scale and prepare for full launch. **Goal:** Validate at scale and prepare for full launch.
### Phase 5: Full Launch ### Phase 5: Full Launch
Open the floodgates. Open the floodgates.
**Actions:** **Actions:**
- Open self-serve signups - Open self-serve signups
- Start charging (if not already) - Start charging (if not already)
- Announce general availability across all channels - Announce general availability across all channels
**Launch touchpoints:** **Launch touchpoints:**
- Customer emails - Customer emails
- In-app popups and product tours - In-app popups and product tours
- Website banner linking to launch assets - Website banner linking to launch assets
@@ -183,11 +208,13 @@ Open the floodgates.
Product Hunt can be powerful for reaching early adopters, but it's not magic—it requires preparation. Product Hunt can be powerful for reaching early adopters, but it's not magic—it requires preparation.
### Pros ### Pros
- Exposure to tech-savvy early adopter audience - Exposure to tech-savvy early adopter audience
- Credibility bump (especially if Product of the Day) - Credibility bump (especially if Product of the Day)
- Potential PR coverage and backlinks - Potential PR coverage and backlinks
### Cons ### Cons
- Very competitive to rank well - Very competitive to rank well
- Short-lived traffic spikes - Short-lived traffic spikes
- Requires significant pre-launch planning - Requires significant pre-launch planning
@@ -195,6 +222,7 @@ Product Hunt can be powerful for reaching early adopters, but it's not magic—i
### How to Launch Successfully ### How to Launch Successfully
**Before launch day:** **Before launch day:**
1. Build relationships with influential supporters, content hubs, and communities 1. Build relationships with influential supporters, content hubs, and communities
2. Optimize your listing: compelling tagline, polished visuals, short demo video 2. Optimize your listing: compelling tagline, polished visuals, short demo video
3. Study successful launches to identify what worked 3. Study successful launches to identify what worked
@@ -202,6 +230,7 @@ Product Hunt can be powerful for reaching early adopters, but it's not magic—i
5. Prepare your team for all-day engagement 5. Prepare your team for all-day engagement
**On launch day:** **On launch day:**
1. Treat it as an all-day event 1. Treat it as an all-day event
2. Respond to every comment in real-time 2. Respond to every comment in real-time
3. Answer questions and spark discussions 3. Answer questions and spark discussions
@@ -209,6 +238,7 @@ Product Hunt can be powerful for reaching early adopters, but it's not magic—i
5. Direct traffic back to your site to capture signups 5. Direct traffic back to your site to capture signups
**After launch day:** **After launch day:**
1. Follow up with everyone who engaged 1. Follow up with everyone who engaged
2. Convert Product Hunt traffic into owned relationships (email signups) 2. Convert Product Hunt traffic into owned relationships (email signups)
3. Continue momentum with post-launch content 3. Continue momentum with post-launch content
@@ -216,12 +246,14 @@ Product Hunt can be powerful for reaching early adopters, but it's not magic—i
### Case Studies ### Case Studies
**SavvyCal** (Scheduling tool): **SavvyCal** (Scheduling tool):
- Optimized landing page and onboarding before launch - Optimized landing page and onboarding before launch
- Built relationships with productivity/SaaS influencers in advance - Built relationships with productivity/SaaS influencers in advance
- Responded to every comment on launch day - Responded to every comment on launch day
- Result: #2 Product of the Month - Result: #2 Product of the Month
**Reform** (Form builder): **Reform** (Form builder):
- Studied successful launches and applied insights - Studied successful launches and applied insights
- Crafted clear tagline, polished visuals, demo video - Crafted clear tagline, polished visuals, demo video
- Engaged in communities before launch (provided value first) - Engaged in communities before launch (provided value first)
@@ -253,6 +285,7 @@ Add dedicated sections about the new feature/product across your site.
Create no-code interactive demo (using tools like Navattic) so visitors can explore before signing up. Create no-code interactive demo (using tools like Navattic) so visitors can explore before signing up.
### Keep Momentum Going ### Keep Momentum Going
It's easier to build on existing momentum than start from scratch. Every touchpoint reinforces the launch. It's easier to build on existing momentum than start from scratch. Every touchpoint reinforces the launch.
--- ---
@@ -266,16 +299,19 @@ Don't rely on a single launch event. Regular updates and feature rollouts sustai
Use this matrix to decide how much marketing each update deserves: Use this matrix to decide how much marketing each update deserves:
**Major updates** (new features, product overhauls): **Major updates** (new features, product overhauls):
- Full campaign across multiple channels - Full campaign across multiple channels
- Blog post, email campaign, in-app messages, social media - Blog post, email campaign, in-app messages, social media
- Maximize exposure - Maximize exposure
**Medium updates** (new integrations, UI enhancements): **Medium updates** (new integrations, UI enhancements):
- Targeted announcement - Targeted announcement
- Email to relevant segments, in-app banner - Email to relevant segments, in-app banner
- Don't need full fanfare - Don't need full fanfare
**Minor updates** (bug fixes, small tweaks): **Minor updates** (bug fixes, small tweaks):
- Changelog and release notes - Changelog and release notes
- Signal that product is improving - Signal that product is improving
- Don't dominate marketing - Don't dominate marketing
@@ -299,6 +335,7 @@ Even small changelog updates remind customers your product is evolving. This bui
## Launch Checklist ## Launch Checklist
### Pre-Launch ### Pre-Launch
- [ ] Landing page with clear value proposition - [ ] Landing page with clear value proposition
- [ ] Email capture / waitlist signup - [ ] Email capture / waitlist signup
- [ ] Early access list built - [ ] Early access list built
@@ -311,6 +348,7 @@ Even small changelog updates remind customers your product is evolving. This bui
- [ ] Analytics/tracking in place - [ ] Analytics/tracking in place
### Launch Day ### Launch Day
- [ ] Announcement email to list - [ ] Announcement email to list
- [ ] Blog post published - [ ] Blog post published
- [ ] Social posts scheduled and posted - [ ] Social posts scheduled and posted
@@ -321,6 +359,7 @@ Even small changelog updates remind customers your product is evolving. This bui
- [ ] Monitor for issues and feedback - [ ] Monitor for issues and feedback
### Post-Launch ### Post-Launch
- [ ] Onboarding email sequence active - [ ] Onboarding email sequence active
- [ ] Follow-up with engaged prospects - [ ] Follow-up with engaged prospects
- [ ] Roundup email includes announcement - [ ] Roundup email includes announcement
+24 -19
View File
@@ -1,6 +1,6 @@
--- ---
name: lint name: lint
description: "Enforce zero-tolerance linting on every code change. This skill MUST be followed after writing or modifying any code file. Run the project linter, fix ALL violations, and never cut corners. Triggers on: lint, delint, fix lint errors, clean up code, run linter." description: 'Enforce zero-tolerance linting on every code change. This skill MUST be followed after writing or modifying any code file. Run the project linter, fix ALL violations, and never cut corners. Triggers on: lint, delint, fix lint errors, clean up code, run linter.'
--- ---
# Lint — Zero Tolerance # Lint — Zero Tolerance
@@ -27,16 +27,16 @@ This is not optional. This is not "nice to have." This is a hard gate.
Check for these in order: Check for these in order:
| File / Config | Linter | Command | | File / Config | Linter | Command |
|---------------|--------|---------| | ----------------------------------------------------------------- | ------------- | ----------------------------------------------------------- |
| `biome.json` or `biome.jsonc` | Biome | `pnpm biome check --write .` or `npx biome check --write .` | | `biome.json` or `biome.jsonc` | Biome | `pnpm biome check --write .` or `npx biome check --write .` |
| `.eslintrc.*` or `eslint.config.*` or `package.json` has `eslint` | ESLint | `pnpm lint` or `npx eslint --fix .` | | `.eslintrc.*` or `eslint.config.*` or `package.json` has `eslint` | ESLint | `pnpm lint` or `npx eslint --fix .` |
| `pyproject.toml` with `[tool.ruff]` | Ruff | `ruff check --fix .` | | `pyproject.toml` with `[tool.ruff]` | Ruff | `ruff check --fix .` |
| `pyproject.toml` with `[tool.flake8]` or `.flake8` | Flake8 | `flake8 .` | | `pyproject.toml` with `[tool.flake8]` or `.flake8` | Flake8 | `flake8 .` |
| `pyproject.toml` with `[tool.pylint]` or `.pylintrc` | Pylint | `pylint **/*.py` | | `pyproject.toml` with `[tool.pylint]` or `.pylintrc` | Pylint | `pylint **/*.py` |
| `.rubocop.yml` | RuboCop | `rubocop -a .` | | `.rubocop.yml` | RuboCop | `rubocop -a .` |
| `Cargo.toml` | Clippy | `cargo clippy --fix` | | `Cargo.toml` | Clippy | `cargo clippy --fix` |
| `.golangci.yml` | golangci-lint | `golangci-lint run --fix` | | `.golangci.yml` | golangci-lint | `golangci-lint run --fix` |
Also check `package.json` scripts for a `lint` or `lint:fix` command — prefer the project's configured command over raw tool invocations. Also check `package.json` scripts for a `lint` or `lint:fix` command — prefer the project's configured command over raw tool invocations.
@@ -64,7 +64,9 @@ If the project doesn't have a `lint` script, run the detected tool directly (see
## Step 3: Fix All Violations ## Step 3: Fix All Violations
### Auto-fixable violations ### Auto-fixable violations
Most linters have a `--fix` flag. Use it first: Most linters have a `--fix` flag. Use it first:
```bash ```bash
pnpm lint --fix pnpm lint --fix
# or # or
@@ -74,25 +76,28 @@ ruff check --fix .
``` ```
### Manual violations ### Manual violations
After auto-fix, re-run the linter without `--fix`. For each remaining error: After auto-fix, re-run the linter without `--fix`. For each remaining error:
1. Read the rule name and understand WHY it exists 1. Read the rule name and understand WHY it exists
2. Fix the code to comply with the rule 2. Fix the code to comply with the rule
3. Do NOT add a disable comment 3. Do NOT add a disable comment
### Common violations and correct fixes ### Common violations and correct fixes
| Violation | Wrong Fix | Right Fix | | Violation | Wrong Fix | Right Fix |
|-----------|-----------|-----------| | ------------------------------------ | ----------------------------- | -------------------------- |
| `no-unused-vars` | `// eslint-disable-next-line` | Delete the unused variable | | `no-unused-vars` | `// eslint-disable-next-line` | Delete the unused variable |
| `@typescript-eslint/no-explicit-any` | `// eslint-disable` | Add a proper type | | `@typescript-eslint/no-explicit-any` | `// eslint-disable` | Add a proper type |
| `prefer-const` | Ignore it | Change `let` to `const` | | `prefer-const` | Ignore it | Change `let` to `const` |
| `no-console` | `// eslint-disable` | Use the project's logger | | `no-console` | `// eslint-disable` | Use the project's logger |
| Import order | Ignore it | Let auto-fix sort imports | | Import order | Ignore it | Let auto-fix sort imports |
| `any` type | `as unknown as Type` | Define the correct type | | `any` type | `as unknown as Type` | Define the correct type |
### The only acceptable exception ### The only acceptable exception
If fixing a violation would require a major refactor outside your task scope: If fixing a violation would require a major refactor outside your task scope:
1. Do NOT disable the rule 1. Do NOT disable the rule
2. Document it as a deferred item with rationale 2. Document it as a deferred item with rationale
3. Create a follow-up task/issue for the fix 3. Create a follow-up task/issue for the fix
@@ -14,6 +14,7 @@ You are a marketing strategist with a library of 139 proven marketing ideas. You
If `.mosaic/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task. If `.mosaic/product-marketing-context.md` exists, read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
When asked for marketing ideas: When asked for marketing ideas:
1. Ask about their product, audience, and current stage if not clear 1. Ask about their product, audience, and current stage if not clear
2. Suggest 3-5 most relevant ideas based on their context 2. Suggest 3-5 most relevant ideas based on their context
3. Provide details on implementation for chosen ideas 3. Provide details on implementation for chosen ideas
@@ -23,25 +24,25 @@ When asked for marketing ideas:
## Ideas by Category (Quick Reference) ## Ideas by Category (Quick Reference)
| Category | Ideas | Examples | | Category | Ideas | Examples |
|----------|-------|----------| | ------------------ | ------- | ----------------------------------------------------------- |
| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing | | Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |
| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu | | Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |
| Free Tools | 14-22 | Calculators, Generators, Chrome extensions | | Free Tools | 14-22 | Calculators, Generators, Chrome extensions |
| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads | | Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |
| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video | | Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |
| Email | 45-53 | Founder emails, Onboarding sequences, Win-back | | Email | 45-53 | Founder emails, Onboarding sequences, Win-back |
| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps | | Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |
| Events | 65-72 | Webinars, Conference speaking, Virtual summits | | Events | 65-72 | Webinars, Conference speaking, Virtual summits |
| PR & Media | 73-76 | Press coverage, Documentaries | | PR & Media | 73-76 | Press coverage, Documentaries |
| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways | | Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |
| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations | | Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |
| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps | | Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |
| Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing | | Unconventional | 110-122 | Awards, Challenges, Guerrilla marketing |
| Platforms | 123-130 | App marketplaces, Review sites, YouTube | | Platforms | 123-130 | App marketplaces, Review sites, YouTube |
| International | 131-132 | Expansion, Price localization | | International | 131-132 | Expansion, Price localization |
| Developer | 133-136 | DevRel, Certifications | | Developer | 133-136 | DevRel, Certifications |
| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language | | Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |
**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md) **For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)
@@ -52,21 +53,25 @@ When asked for marketing ideas:
### By Stage ### By Stage
**Pre-launch:** **Pre-launch:**
- Waitlist referrals (#79) - Waitlist referrals (#79)
- Early access pricing (#81) - Early access pricing (#81)
- Product Hunt prep (#78) - Product Hunt prep (#78)
**Early stage:** **Early stage:**
- Content & SEO (#1-10) - Content & SEO (#1-10)
- Community (#35) - Community (#35)
- Founder-led sales (#47) - Founder-led sales (#47)
**Growth stage:** **Growth stage:**
- Paid acquisition (#23-34) - Paid acquisition (#23-34)
- Partnerships (#54-64) - Partnerships (#54-64)
- Events (#65-72) - Events (#65-72)
**Scale:** **Scale:**
- Brand campaigns - Brand campaigns
- International (#131-132) - International (#131-132)
- Media acquisitions (#73) - Media acquisitions (#73)
@@ -74,22 +79,26 @@ When asked for marketing ideas:
### By Budget ### By Budget
**Free:** **Free:**
- Content & SEO - Content & SEO
- Community building - Community building
- Social media - Social media
- Comment marketing - Comment marketing
**Low budget:** **Low budget:**
- Targeted ads - Targeted ads
- Sponsorships - Sponsorships
- Free tools - Free tools
**Medium budget:** **Medium budget:**
- Events - Events
- Partnerships - Partnerships
- PR - PR
**High budget:** **High budget:**
- Acquisitions - Acquisitions
- Conferences - Conferences
- Brand campaigns - Brand campaigns
@@ -97,12 +106,15 @@ When asked for marketing ideas:
### By Timeline ### By Timeline
**Quick wins:** **Quick wins:**
- Ads, email, social posts - Ads, email, social posts
**Medium-term:** **Medium-term:**
- Content, SEO, community - Content, SEO, community
**Long-term:** **Long-term:**
- Brand, thought leadership, platform effects - Brand, thought leadership, platform effects
--- ---
@@ -110,26 +122,31 @@ When asked for marketing ideas:
## Top Ideas by Use Case ## Top Ideas by Use Case
### Need Leads Fast ### Need Leads Fast
- Google Ads (#31) - High-intent search - Google Ads (#31) - High-intent search
- LinkedIn Ads (#28) - B2B targeting - LinkedIn Ads (#28) - B2B targeting
- Engineering as Marketing (#15) - Free tool lead gen - Engineering as Marketing (#15) - Free tool lead gen
### Building Authority ### Building Authority
- Conference Speaking (#70) - Conference Speaking (#70)
- Book Marketing (#104) - Book Marketing (#104)
- Podcasts (#107) - Podcasts (#107)
### Low Budget Growth ### Low Budget Growth
- Easy Keyword Ranking (#1) - Easy Keyword Ranking (#1)
- Reddit Marketing (#38) - Reddit Marketing (#38)
- Comment Marketing (#44) - Comment Marketing (#44)
### Product-Led Growth ### Product-Led Growth
- Viral Loops (#93) - Viral Loops (#93)
- Powered By Marketing (#87) - Powered By Marketing (#87)
- In-App Upsells (#91) - In-App Upsells (#91)
### Enterprise Sales ### Enterprise Sales
- Investor Marketing (#133) - Investor Marketing (#133)
- Expert Networks (#57) - Expert Networks (#57)
- Conference Sponsorship (#72) - Conference Sponsorship (#72)
@@ -246,25 +246,25 @@ Complete list of proven marketing approaches organized by category.
99. **Graphic Novel Marketing** - Transform complex stories into visual narratives. 99. **Graphic Novel Marketing** - Transform complex stories into visual narratives.
100. **Promo Videos** - High-quality promotional videos showcase your product. 100. **Promo Videos** - High-quality promotional videos showcase your product.
101. **Industry Interviews** - Interview customers, experts, and thought leaders. 101. **Industry Interviews** - Interview customers, experts, and thought leaders.
102. **Social Screenshots** - Design shareable screenshot templates for social proof. 102. **Social Screenshots** - Design shareable screenshot templates for social proof.
103. **Online Courses** - Educational courses establish authority while generating leads. 103. **Online Courses** - Educational courses establish authority while generating leads.
104. **Book Marketing** - Author a book establishing expertise in your domain. 104. **Book Marketing** - Author a book establishing expertise in your domain.
105. **Annual Reports** - Publish annual reports showcasing industry data and trends. 105. **Annual Reports** - Publish annual reports showcasing industry data and trends.
106. **End of Year Wraps** - Personalized year-end summaries users want to share. 106. **End of Year Wraps** - Personalized year-end summaries users want to share.
107. **Podcasts** - Launch a podcast reaching audiences during commutes. 107. **Podcasts** - Launch a podcast reaching audiences during commutes.
108. **Changelogs** - Public changelogs showcase product momentum. 108. **Changelogs** - Public changelogs showcase product momentum.
109. **Public Demos** - Live product demonstrations showing real usage. 109. **Public Demos** - Live product demonstrations showing real usage.
--- ---
@@ -27,71 +27,85 @@ Mental models are thinking tools that help you make better decisions, understand
These models sharpen your strategy and help you solve the right problems. These models sharpen your strategy and help you solve the right problems.
### First Principles ### First Principles
Break problems down to basic truths and build solutions from there. Instead of copying competitors, ask "why" repeatedly to find root causes. Use the 5 Whys technique to tunnel down to what really matters. Break problems down to basic truths and build solutions from there. Instead of copying competitors, ask "why" repeatedly to find root causes. Use the 5 Whys technique to tunnel down to what really matters.
**Marketing application**: Don't assume you need content marketing because competitors do. Ask why you need it, what problem it solves, and whether there's a better solution. **Marketing application**: Don't assume you need content marketing because competitors do. Ask why you need it, what problem it solves, and whether there's a better solution.
### Jobs to Be Done ### Jobs to Be Done
People don't buy products—they "hire" them to get a job done. Focus on the outcome customers want, not features. People don't buy products—they "hire" them to get a job done. Focus on the outcome customers want, not features.
**Marketing application**: A drill buyer doesn't want a drill—they want a hole. Frame your product around the job it accomplishes, not its specifications. **Marketing application**: A drill buyer doesn't want a drill—they want a hole. Frame your product around the job it accomplishes, not its specifications.
### Circle of Competence ### Circle of Competence
Know what you're good at and stay within it. Venture outside only with proper learning or expert help. Know what you're good at and stay within it. Venture outside only with proper learning or expert help.
**Marketing application**: Don't chase every channel. Double down where you have genuine expertise and competitive advantage. **Marketing application**: Don't chase every channel. Double down where you have genuine expertise and competitive advantage.
### Inversion ### Inversion
Instead of asking "How do I succeed?", ask "What would guarantee failure?" Then avoid those things. Instead of asking "How do I succeed?", ask "What would guarantee failure?" Then avoid those things.
**Marketing application**: List everything that would make your campaign fail—confusing messaging, wrong audience, slow landing page—then systematically prevent each. **Marketing application**: List everything that would make your campaign fail—confusing messaging, wrong audience, slow landing page—then systematically prevent each.
### Occam's Razor ### Occam's Razor
The simplest explanation is usually correct. Avoid overcomplicating strategies or attributing results to complex causes when simple ones suffice. The simplest explanation is usually correct. Avoid overcomplicating strategies or attributing results to complex causes when simple ones suffice.
**Marketing application**: If conversions dropped, check the obvious first (broken form, page speed) before assuming complex attribution issues. **Marketing application**: If conversions dropped, check the obvious first (broken form, page speed) before assuming complex attribution issues.
### Pareto Principle (80/20 Rule) ### Pareto Principle (80/20 Rule)
Roughly 80% of results come from 20% of efforts. Identify and focus on the vital few. Roughly 80% of results come from 20% of efforts. Identify and focus on the vital few.
**Marketing application**: Find the 20% of channels, customers, or content driving 80% of results. Cut or reduce the rest. **Marketing application**: Find the 20% of channels, customers, or content driving 80% of results. Cut or reduce the rest.
### Local vs. Global Optima ### Local vs. Global Optima
A local optimum is the best solution nearby, but a global optimum is the best overall. Don't get stuck optimizing the wrong thing. A local optimum is the best solution nearby, but a global optimum is the best overall. Don't get stuck optimizing the wrong thing.
**Marketing application**: Optimizing email subject lines (local) won't help if email isn't the right channel (global). Zoom out before zooming in. **Marketing application**: Optimizing email subject lines (local) won't help if email isn't the right channel (global). Zoom out before zooming in.
### Theory of Constraints ### Theory of Constraints
Every system has one bottleneck limiting throughput. Find and fix that constraint before optimizing elsewhere. Every system has one bottleneck limiting throughput. Find and fix that constraint before optimizing elsewhere.
**Marketing application**: If your funnel converts well but traffic is low, more conversion optimization won't help. Fix the traffic bottleneck first. **Marketing application**: If your funnel converts well but traffic is low, more conversion optimization won't help. Fix the traffic bottleneck first.
### Opportunity Cost ### Opportunity Cost
Every choice has a cost—what you give up by not choosing alternatives. Consider what you're saying no to. Every choice has a cost—what you give up by not choosing alternatives. Consider what you're saying no to.
**Marketing application**: Time spent on a low-ROI channel is time not spent on high-ROI activities. Always compare against alternatives. **Marketing application**: Time spent on a low-ROI channel is time not spent on high-ROI activities. Always compare against alternatives.
### Law of Diminishing Returns ### Law of Diminishing Returns
After a point, additional investment yields progressively smaller gains. After a point, additional investment yields progressively smaller gains.
**Marketing application**: The 10th blog post won't have the same impact as the first. Know when to diversify rather than double down. **Marketing application**: The 10th blog post won't have the same impact as the first. Know when to diversify rather than double down.
### Second-Order Thinking ### Second-Order Thinking
Consider not just immediate effects, but the effects of those effects. Consider not just immediate effects, but the effects of those effects.
**Marketing application**: A flash sale boosts revenue (first order) but may train customers to wait for discounts (second order). **Marketing application**: A flash sale boosts revenue (first order) but may train customers to wait for discounts (second order).
### Map ≠ Territory ### Map ≠ Territory
Models and data represent reality but aren't reality itself. Don't confuse your analytics dashboard with actual customer experience. Models and data represent reality but aren't reality itself. Don't confuse your analytics dashboard with actual customer experience.
**Marketing application**: Your customer persona is a useful model, but real customers are more complex. Stay in touch with actual users. **Marketing application**: Your customer persona is a useful model, but real customers are more complex. Stay in touch with actual users.
### Probabilistic Thinking ### Probabilistic Thinking
Think in probabilities, not certainties. Estimate likelihoods and plan for multiple outcomes. Think in probabilities, not certainties. Estimate likelihoods and plan for multiple outcomes.
**Marketing application**: Don't bet everything on one campaign. Spread risk and plan for scenarios where your primary strategy underperforms. **Marketing application**: Don't bet everything on one campaign. Spread risk and plan for scenarios where your primary strategy underperforms.
### Barbell Strategy ### Barbell Strategy
Combine extreme safety with small high-risk/high-reward bets. Avoid the mediocre middle. Combine extreme safety with small high-risk/high-reward bets. Avoid the mediocre middle.
**Marketing application**: Put 80% of budget into proven channels, 20% into experimental bets. Avoid moderate-risk, moderate-reward middle. **Marketing application**: Put 80% of budget into proven channels, 20% into experimental bets. Avoid moderate-risk, moderate-reward middle.
@@ -103,111 +117,133 @@ Combine extreme safety with small high-risk/high-reward bets. Avoid the mediocre
These models explain how customers think, decide, and behave. These models explain how customers think, decide, and behave.
### Fundamental Attribution Error ### Fundamental Attribution Error
People attribute others' behavior to character, not circumstances. "They didn't buy because they're not serious" vs. "The checkout was confusing." People attribute others' behavior to character, not circumstances. "They didn't buy because they're not serious" vs. "The checkout was confusing."
**Marketing application**: When customers don't convert, examine your process before blaming them. The problem is usually situational, not personal. **Marketing application**: When customers don't convert, examine your process before blaming them. The problem is usually situational, not personal.
### Mere Exposure Effect ### Mere Exposure Effect
People prefer things they've seen before. Familiarity breeds liking. People prefer things they've seen before. Familiarity breeds liking.
**Marketing application**: Consistent brand presence builds preference over time. Repetition across channels creates comfort and trust. **Marketing application**: Consistent brand presence builds preference over time. Repetition across channels creates comfort and trust.
### Availability Heuristic ### Availability Heuristic
People judge likelihood by how easily examples come to mind. Recent or vivid events seem more common. People judge likelihood by how easily examples come to mind. Recent or vivid events seem more common.
**Marketing application**: Case studies and testimonials make success feel more achievable. Make positive outcomes easy to imagine. **Marketing application**: Case studies and testimonials make success feel more achievable. Make positive outcomes easy to imagine.
### Confirmation Bias ### Confirmation Bias
People seek information confirming existing beliefs and ignore contradictory evidence. People seek information confirming existing beliefs and ignore contradictory evidence.
**Marketing application**: Understand what your audience already believes and align messaging accordingly. Fighting beliefs head-on rarely works. **Marketing application**: Understand what your audience already believes and align messaging accordingly. Fighting beliefs head-on rarely works.
### The Lindy Effect ### The Lindy Effect
The longer something has survived, the longer it's likely to continue. Old ideas often outlast new ones. The longer something has survived, the longer it's likely to continue. Old ideas often outlast new ones.
**Marketing application**: Proven marketing principles (clear value props, social proof) outlast trendy tactics. Don't abandon fundamentals for fads. **Marketing application**: Proven marketing principles (clear value props, social proof) outlast trendy tactics. Don't abandon fundamentals for fads.
### Mimetic Desire ### Mimetic Desire
People want things because others want them. Desire is socially contagious. People want things because others want them. Desire is socially contagious.
**Marketing application**: Show that desirable people want your product. Waitlists, exclusivity, and social proof trigger mimetic desire. **Marketing application**: Show that desirable people want your product. Waitlists, exclusivity, and social proof trigger mimetic desire.
### Sunk Cost Fallacy ### Sunk Cost Fallacy
People continue investing in something because of past investment, even when it's no longer rational. People continue investing in something because of past investment, even when it's no longer rational.
**Marketing application**: Know when to kill underperforming campaigns. Past spend shouldn't justify future spend if results aren't there. **Marketing application**: Know when to kill underperforming campaigns. Past spend shouldn't justify future spend if results aren't there.
### Endowment Effect ### Endowment Effect
People value things more once they own them. People value things more once they own them.
**Marketing application**: Free trials, samples, and freemium models let customers "own" the product, making them reluctant to give it up. **Marketing application**: Free trials, samples, and freemium models let customers "own" the product, making them reluctant to give it up.
### IKEA Effect ### IKEA Effect
People value things more when they've put effort into creating them. People value things more when they've put effort into creating them.
**Marketing application**: Let customers customize, configure, or build something. Their investment increases perceived value and commitment. **Marketing application**: Let customers customize, configure, or build something. Their investment increases perceived value and commitment.
### Zero-Price Effect ### Zero-Price Effect
Free isn't just a low price—it's psychologically different. "Free" triggers irrational preference. Free isn't just a low price—it's psychologically different. "Free" triggers irrational preference.
**Marketing application**: Free tiers, free trials, and free shipping have disproportionate appeal. The jump from $1 to $0 is bigger than $2 to $1. **Marketing application**: Free tiers, free trials, and free shipping have disproportionate appeal. The jump from $1 to $0 is bigger than $2 to $1.
### Hyperbolic Discounting / Present Bias ### Hyperbolic Discounting / Present Bias
People strongly prefer immediate rewards over future ones, even when waiting is more rational. People strongly prefer immediate rewards over future ones, even when waiting is more rational.
**Marketing application**: Emphasize immediate benefits ("Start saving time today") over future ones ("You'll see ROI in 6 months"). **Marketing application**: Emphasize immediate benefits ("Start saving time today") over future ones ("You'll see ROI in 6 months").
### Status-Quo Bias ### Status-Quo Bias
People prefer the current state of affairs. Change requires effort and feels risky. People prefer the current state of affairs. Change requires effort and feels risky.
**Marketing application**: Reduce friction to switch. Make the transition feel safe and easy. "Import your data in one click." **Marketing application**: Reduce friction to switch. Make the transition feel safe and easy. "Import your data in one click."
### Default Effect ### Default Effect
People tend to accept pre-selected options. Defaults are powerful. People tend to accept pre-selected options. Defaults are powerful.
**Marketing application**: Pre-select the plan you want customers to choose. Opt-out beats opt-in for subscriptions (ethically applied). **Marketing application**: Pre-select the plan you want customers to choose. Opt-out beats opt-in for subscriptions (ethically applied).
### Paradox of Choice ### Paradox of Choice
Too many options overwhelm and paralyze. Fewer choices often lead to more decisions. Too many options overwhelm and paralyze. Fewer choices often lead to more decisions.
**Marketing application**: Limit options. Three pricing tiers beat seven. Recommend a single "best for most" option. **Marketing application**: Limit options. Three pricing tiers beat seven. Recommend a single "best for most" option.
### Goal-Gradient Effect ### Goal-Gradient Effect
People accelerate effort as they approach a goal. Progress visualization motivates action. People accelerate effort as they approach a goal. Progress visualization motivates action.
**Marketing application**: Show progress bars, completion percentages, and "almost there" messaging to drive completion. **Marketing application**: Show progress bars, completion percentages, and "almost there" messaging to drive completion.
### Peak-End Rule ### Peak-End Rule
People judge experiences by the peak (best or worst moment) and the end, not the average. People judge experiences by the peak (best or worst moment) and the end, not the average.
**Marketing application**: Design memorable peaks (surprise upgrades, delightful moments) and strong endings (thank you pages, follow-up emails). **Marketing application**: Design memorable peaks (surprise upgrades, delightful moments) and strong endings (thank you pages, follow-up emails).
### Zeigarnik Effect ### Zeigarnik Effect
Unfinished tasks occupy the mind more than completed ones. Open loops create tension. Unfinished tasks occupy the mind more than completed ones. Open loops create tension.
**Marketing application**: "You're 80% done" creates pull to finish. Incomplete profiles, abandoned carts, and cliffhangers leverage this. **Marketing application**: "You're 80% done" creates pull to finish. Incomplete profiles, abandoned carts, and cliffhangers leverage this.
### Pratfall Effect ### Pratfall Effect
Competent people become more likable when they show a small flaw. Perfection is less relatable. Competent people become more likable when they show a small flaw. Perfection is less relatable.
**Marketing application**: Admitting a weakness ("We're not the cheapest, but...") can increase trust and differentiation. **Marketing application**: Admitting a weakness ("We're not the cheapest, but...") can increase trust and differentiation.
### Curse of Knowledge ### Curse of Knowledge
Once you know something, you can't imagine not knowing it. Experts struggle to explain simply. Once you know something, you can't imagine not knowing it. Experts struggle to explain simply.
**Marketing application**: Your product seems obvious to you but confusing to newcomers. Test copy with people unfamiliar with your space. **Marketing application**: Your product seems obvious to you but confusing to newcomers. Test copy with people unfamiliar with your space.
### Mental Accounting ### Mental Accounting
People treat money differently based on its source or intended use, even though money is fungible. People treat money differently based on its source or intended use, even though money is fungible.
**Marketing application**: Frame costs in favorable mental accounts. "$3/day" feels different than "$90/month" even though it's the same. **Marketing application**: Frame costs in favorable mental accounts. "$3/day" feels different than "$90/month" even though it's the same.
### Regret Aversion ### Regret Aversion
People avoid actions that might cause regret, even if the expected outcome is positive. People avoid actions that might cause regret, even if the expected outcome is positive.
**Marketing application**: Address regret directly. Money-back guarantees, free trials, and "no commitment" messaging reduce regret fear. **Marketing application**: Address regret directly. Money-back guarantees, free trials, and "no commitment" messaging reduce regret fear.
### Bandwagon Effect / Social Proof ### Bandwagon Effect / Social Proof
People follow what others are doing. Popularity signals quality and safety. People follow what others are doing. Popularity signals quality and safety.
**Marketing application**: Show customer counts, testimonials, logos, reviews, and "trending" indicators. Numbers create confidence. **Marketing application**: Show customer counts, testimonials, logos, reviews, and "trending" indicators. Numbers create confidence.
@@ -219,66 +255,79 @@ People follow what others are doing. Popularity signals quality and safety.
These models help you ethically influence customer decisions. These models help you ethically influence customer decisions.
### Reciprocity Principle ### Reciprocity Principle
People feel obligated to return favors. Give first, and people want to give back. People feel obligated to return favors. Give first, and people want to give back.
**Marketing application**: Free content, free tools, and generous free tiers create reciprocal obligation. Give value before asking for anything. **Marketing application**: Free content, free tools, and generous free tiers create reciprocal obligation. Give value before asking for anything.
### Commitment & Consistency ### Commitment & Consistency
Once people commit to something, they want to stay consistent with that commitment. Once people commit to something, they want to stay consistent with that commitment.
**Marketing application**: Get small commitments first (email signup, free trial). People who've taken one step are more likely to take the next. **Marketing application**: Get small commitments first (email signup, free trial). People who've taken one step are more likely to take the next.
### Authority Bias ### Authority Bias
People defer to experts and authority figures. Credentials and expertise create trust. People defer to experts and authority figures. Credentials and expertise create trust.
**Marketing application**: Feature expert endorsements, certifications, "featured in" logos, and thought leadership content. **Marketing application**: Feature expert endorsements, certifications, "featured in" logos, and thought leadership content.
### Liking / Similarity Bias ### Liking / Similarity Bias
People say yes to those they like and those similar to themselves. People say yes to those they like and those similar to themselves.
**Marketing application**: Use relatable spokespeople, founder stories, and community language. "Built by marketers for marketers" signals similarity. **Marketing application**: Use relatable spokespeople, founder stories, and community language. "Built by marketers for marketers" signals similarity.
### Unity Principle ### Unity Principle
Shared identity drives influence. "One of us" is powerful. Shared identity drives influence. "One of us" is powerful.
**Marketing application**: Position your brand as part of the customer's tribe. Use insider language and shared values. **Marketing application**: Position your brand as part of the customer's tribe. Use insider language and shared values.
### Scarcity / Urgency Heuristic ### Scarcity / Urgency Heuristic
Limited availability increases perceived value. Scarcity signals desirability. Limited availability increases perceived value. Scarcity signals desirability.
**Marketing application**: Limited-time offers, low-stock warnings, and exclusive access create urgency. Only use when genuine. **Marketing application**: Limited-time offers, low-stock warnings, and exclusive access create urgency. Only use when genuine.
### Foot-in-the-Door Technique ### Foot-in-the-Door Technique
Start with a small request, then escalate. Compliance with small requests leads to compliance with larger ones. Start with a small request, then escalate. Compliance with small requests leads to compliance with larger ones.
**Marketing application**: Free trial → paid plan → annual plan → enterprise. Each step builds on the last. **Marketing application**: Free trial → paid plan → annual plan → enterprise. Each step builds on the last.
### Door-in-the-Face Technique ### Door-in-the-Face Technique
Start with an unreasonably large request, then retreat to what you actually want. The contrast makes the second request seem reasonable. Start with an unreasonably large request, then retreat to what you actually want. The contrast makes the second request seem reasonable.
**Marketing application**: Show enterprise pricing first, then reveal the affordable starter plan. The contrast makes it feel like a deal. **Marketing application**: Show enterprise pricing first, then reveal the affordable starter plan. The contrast makes it feel like a deal.
### Loss Aversion / Prospect Theory ### Loss Aversion / Prospect Theory
Losses feel roughly twice as painful as equivalent gains feel good. People will work harder to avoid losing than to gain. Losses feel roughly twice as painful as equivalent gains feel good. People will work harder to avoid losing than to gain.
**Marketing application**: Frame in terms of what they'll lose by not acting. "Don't miss out" beats "You could gain." **Marketing application**: Frame in terms of what they'll lose by not acting. "Don't miss out" beats "You could gain."
### Anchoring Effect ### Anchoring Effect
The first number people see heavily influences subsequent judgments. The first number people see heavily influences subsequent judgments.
**Marketing application**: Show the higher price first (original price, competitor price, enterprise tier) to anchor expectations. **Marketing application**: Show the higher price first (original price, competitor price, enterprise tier) to anchor expectations.
### Decoy Effect ### Decoy Effect
Adding a third, inferior option makes one of the original two look better. Adding a third, inferior option makes one of the original two look better.
**Marketing application**: A "decoy" pricing tier that's clearly worse value makes your preferred tier look like the obvious choice. **Marketing application**: A "decoy" pricing tier that's clearly worse value makes your preferred tier look like the obvious choice.
### Framing Effect ### Framing Effect
How something is presented changes how it's perceived. Same facts, different frames. How something is presented changes how it's perceived. Same facts, different frames.
**Marketing application**: "90% success rate" vs. "10% failure rate" are identical but feel different. Frame positively. **Marketing application**: "90% success rate" vs. "10% failure rate" are identical but feel different. Frame positively.
### Contrast Effect ### Contrast Effect
Things seem different depending on what they're compared to. Things seem different depending on what they're compared to.
**Marketing application**: Show the "before" state clearly. The contrast with your "after" makes improvements vivid. **Marketing application**: Show the "before" state clearly. The contrast with your "after" makes improvements vivid.
@@ -290,26 +339,31 @@ Things seem different depending on what they're compared to.
These models specifically address how people perceive and respond to prices. These models specifically address how people perceive and respond to prices.
### Charm Pricing / Left-Digit Effect ### Charm Pricing / Left-Digit Effect
Prices ending in 9 seem significantly lower than the next round number. $99 feels much cheaper than $100. Prices ending in 9 seem significantly lower than the next round number. $99 feels much cheaper than $100.
**Marketing application**: Use .99 or .95 endings for value-focused products. The left digit dominates perception. **Marketing application**: Use .99 or .95 endings for value-focused products. The left digit dominates perception.
### Rounded-Price (Fluency) Effect ### Rounded-Price (Fluency) Effect
Round numbers feel premium and are easier to process. $100 signals quality; $99 signals value. Round numbers feel premium and are easier to process. $100 signals quality; $99 signals value.
**Marketing application**: Use round prices for premium products ($500/month), charm prices for value products ($497/month). **Marketing application**: Use round prices for premium products ($500/month), charm prices for value products ($497/month).
### Rule of 100 ### Rule of 100
For prices under $100, percentage discounts seem larger ("20% off"). For prices over $100, absolute discounts seem larger ("$50 off"). For prices under $100, percentage discounts seem larger ("20% off"). For prices over $100, absolute discounts seem larger ("$50 off").
**Marketing application**: $80 product: "20% off" beats "$16 off." $500 product: "$100 off" beats "20% off." **Marketing application**: $80 product: "20% off" beats "$16 off." $500 product: "$100 off" beats "20% off."
### Price Relativity / Good-Better-Best ### Price Relativity / Good-Better-Best
People judge prices relative to options presented. A middle tier seems reasonable between cheap and expensive. People judge prices relative to options presented. A middle tier seems reasonable between cheap and expensive.
**Marketing application**: Three tiers where the middle is your target. The expensive tier makes it look reasonable; the cheap tier provides an anchor. **Marketing application**: Three tiers where the middle is your target. The expensive tier makes it look reasonable; the cheap tier provides an anchor.
### Mental Accounting (Pricing) ### Mental Accounting (Pricing)
Framing the same price differently changes perception. Framing the same price differently changes perception.
**Marketing application**: "$1/day" feels cheaper than "$30/month." "Less than your morning coffee" reframes the expense. **Marketing application**: "$1/day" feels cheaper than "$30/month." "Less than your morning coffee" reframes the expense.
@@ -321,51 +375,61 @@ Framing the same price differently changes perception.
These models help you design effective marketing systems. These models help you design effective marketing systems.
### Hick's Law ### Hick's Law
Decision time increases with the number and complexity of choices. More options = slower decisions = more abandonment. Decision time increases with the number and complexity of choices. More options = slower decisions = more abandonment.
**Marketing application**: Simplify choices. One clear CTA beats three. Fewer form fields beat more. **Marketing application**: Simplify choices. One clear CTA beats three. Fewer form fields beat more.
### AIDA Funnel ### AIDA Funnel
Attention → Interest → Desire → Action. The classic customer journey model. Attention → Interest → Desire → Action. The classic customer journey model.
**Marketing application**: Structure pages and campaigns to move through each stage. Capture attention before building desire. **Marketing application**: Structure pages and campaigns to move through each stage. Capture attention before building desire.
### Rule of 7 ### Rule of 7
Prospects need roughly 7 touchpoints before converting. One ad rarely converts; sustained presence does. Prospects need roughly 7 touchpoints before converting. One ad rarely converts; sustained presence does.
**Marketing application**: Build multi-touch campaigns across channels. Retargeting, email sequences, and consistent presence compound. **Marketing application**: Build multi-touch campaigns across channels. Retargeting, email sequences, and consistent presence compound.
### Nudge Theory / Choice Architecture ### Nudge Theory / Choice Architecture
Small changes in how choices are presented significantly influence decisions. Small changes in how choices are presented significantly influence decisions.
**Marketing application**: Default selections, strategic ordering, and friction reduction guide behavior without restricting choice. **Marketing application**: Default selections, strategic ordering, and friction reduction guide behavior without restricting choice.
### BJ Fogg Behavior Model ### BJ Fogg Behavior Model
Behavior = Motivation × Ability × Prompt. All three must be present for action. Behavior = Motivation × Ability × Prompt. All three must be present for action.
**Marketing application**: High motivation but hard to do = won't happen. Easy to do but no prompt = won't happen. Design for all three. **Marketing application**: High motivation but hard to do = won't happen. Easy to do but no prompt = won't happen. Design for all three.
### EAST Framework ### EAST Framework
Make desired behaviors: Easy, Attractive, Social, Timely. Make desired behaviors: Easy, Attractive, Social, Timely.
**Marketing application**: Reduce friction (easy), make it appealing (attractive), show others doing it (social), ask at the right moment (timely). **Marketing application**: Reduce friction (easy), make it appealing (attractive), show others doing it (social), ask at the right moment (timely).
### COM-B Model ### COM-B Model
Behavior requires: Capability, Opportunity, Motivation. Behavior requires: Capability, Opportunity, Motivation.
**Marketing application**: Can they do it (capability)? Is the path clear (opportunity)? Do they want to (motivation)? Address all three. **Marketing application**: Can they do it (capability)? Is the path clear (opportunity)? Do they want to (motivation)? Address all three.
### Activation Energy ### Activation Energy
The initial energy required to start something. High activation energy prevents action even if the task is easy overall. The initial energy required to start something. High activation energy prevents action even if the task is easy overall.
**Marketing application**: Reduce starting friction. Pre-fill forms, offer templates, show quick wins. Make the first step trivially easy. **Marketing application**: Reduce starting friction. Pre-fill forms, offer templates, show quick wins. Make the first step trivially easy.
### North Star Metric ### North Star Metric
One metric that best captures the value you deliver to customers. Focus creates alignment. One metric that best captures the value you deliver to customers. Focus creates alignment.
**Marketing application**: Identify your North Star (active users, completed projects, revenue per customer) and align all efforts toward it. **Marketing application**: Identify your North Star (active users, completed projects, revenue per customer) and align all efforts toward it.
### The Cobra Effect ### The Cobra Effect
When incentives backfire and produce the opposite of intended results. When incentives backfire and produce the opposite of intended results.
**Marketing application**: Test incentive structures. A referral bonus might attract low-quality referrals gaming the system. **Marketing application**: Test incentive structures. A referral bonus might attract low-quality referrals gaming the system.
@@ -377,41 +441,49 @@ When incentives backfire and produce the opposite of intended results.
These models explain how marketing compounds and scales. These models explain how marketing compounds and scales.
### Feedback Loops ### Feedback Loops
Output becomes input, creating cycles. Positive loops accelerate growth; negative loops create decline. Output becomes input, creating cycles. Positive loops accelerate growth; negative loops create decline.
**Marketing application**: Build virtuous cycles: more users → more content → better SEO → more users. Identify and strengthen positive loops. **Marketing application**: Build virtuous cycles: more users → more content → better SEO → more users. Identify and strengthen positive loops.
### Compounding ### Compounding
Small, consistent gains accumulate into large results over time. Early gains matter most. Small, consistent gains accumulate into large results over time. Early gains matter most.
**Marketing application**: Consistent content, SEO, and brand building compound. Start early; benefits accumulate exponentially. **Marketing application**: Consistent content, SEO, and brand building compound. Start early; benefits accumulate exponentially.
### Network Effects ### Network Effects
A product becomes more valuable as more people use it. A product becomes more valuable as more people use it.
**Marketing application**: Design features that improve with more users: shared workspaces, integrations, marketplaces, communities. **Marketing application**: Design features that improve with more users: shared workspaces, integrations, marketplaces, communities.
### Flywheel Effect ### Flywheel Effect
Sustained effort creates momentum that eventually maintains itself. Hard to start, easy to maintain. Sustained effort creates momentum that eventually maintains itself. Hard to start, easy to maintain.
**Marketing application**: Content → traffic → leads → customers → case studies → more content. Each element powers the next. **Marketing application**: Content → traffic → leads → customers → case studies → more content. Each element powers the next.
### Switching Costs ### Switching Costs
The price (time, money, effort, data) of changing to a competitor. High switching costs create retention. The price (time, money, effort, data) of changing to a competitor. High switching costs create retention.
**Marketing application**: Increase switching costs ethically: integrations, data accumulation, workflow customization, team adoption. **Marketing application**: Increase switching costs ethically: integrations, data accumulation, workflow customization, team adoption.
### Exploration vs. Exploitation ### Exploration vs. Exploitation
Balance trying new things (exploration) with optimizing what works (exploitation). Balance trying new things (exploration) with optimizing what works (exploitation).
**Marketing application**: Don't abandon working channels for shiny new ones, but allocate some budget to experiments. **Marketing application**: Don't abandon working channels for shiny new ones, but allocate some budget to experiments.
### Critical Mass / Tipping Point ### Critical Mass / Tipping Point
The threshold after which growth becomes self-sustaining. The threshold after which growth becomes self-sustaining.
**Marketing application**: Focus resources on reaching critical mass in one segment before expanding. Depth before breadth. **Marketing application**: Focus resources on reaching critical mass in one segment before expanding. Depth before breadth.
### Survivorship Bias ### Survivorship Bias
Focusing on successes while ignoring failures that aren't visible. Focusing on successes while ignoring failures that aren't visible.
**Marketing application**: Study failed campaigns, not just successful ones. The viral hit you're copying had 99 failures you didn't see. **Marketing application**: Study failed campaigns, not just successful ones. The viral hit you're copying had 99 failures you didn't see.
@@ -422,16 +494,16 @@ Focusing on successes while ignoring failures that aren't visible.
When facing a marketing challenge, consider: When facing a marketing challenge, consider:
| Challenge | Relevant Models | | Challenge | Relevant Models |
|-----------|-----------------| | ------------------ | ---------------------------------------------------------- |
| Low conversions | Hick's Law, Activation Energy, BJ Fogg, Friction | | Low conversions | Hick's Law, Activation Energy, BJ Fogg, Friction |
| Price objections | Anchoring, Framing, Mental Accounting, Loss Aversion | | Price objections | Anchoring, Framing, Mental Accounting, Loss Aversion |
| Building trust | Authority, Social Proof, Reciprocity, Pratfall Effect | | Building trust | Authority, Social Proof, Reciprocity, Pratfall Effect |
| Increasing urgency | Scarcity, Loss Aversion, Zeigarnik Effect | | Increasing urgency | Scarcity, Loss Aversion, Zeigarnik Effect |
| Retention/churn | Endowment Effect, Switching Costs, Status-Quo Bias | | Retention/churn | Endowment Effect, Switching Costs, Status-Quo Bias |
| Growth stalling | Theory of Constraints, Local vs Global Optima, Compounding | | Growth stalling | Theory of Constraints, Local vs Global Optima, Compounding |
| Decision paralysis | Paradox of Choice, Default Effect, Nudge Theory | | Decision paralysis | Paradox of Choice, Default Effect, Nudge Theory |
| Onboarding | Goal-Gradient, IKEA Effect, Commitment & Consistency | | Onboarding | Goal-Gradient, IKEA Effect, Commitment & Consistency |
--- ---
@@ -43,6 +43,7 @@ Start with the sitemap to find relevant pages: `https://modelcontextprotocol.io/
Then fetch specific pages with `.md` suffix for markdown format (e.g., `https://modelcontextprotocol.io/specification/draft.md`). Then fetch specific pages with `.md` suffix for markdown format (e.g., `https://modelcontextprotocol.io/specification/draft.md`).
Key pages to review: Key pages to review:
- Specification overview and architecture - Specification overview and architecture
- Transport mechanisms (streamable HTTP, stdio) - Transport mechanisms (streamable HTTP, stdio)
- Tool, resource, and prompt definitions - Tool, resource, and prompt definitions
@@ -50,6 +51,7 @@ Key pages to review:
#### 1.3 Study Framework Documentation #### 1.3 Study Framework Documentation
**Recommended stack:** **Recommended stack:**
- **Language**: TypeScript (high-quality SDK support and good compatibility in many execution environments e.g. MCPB. Plus AI models are good at generating TypeScript code, benefiting from its broad usage, static typing and good linting tools) - **Language**: TypeScript (high-quality SDK support and good compatibility in many execution environments e.g. MCPB. Plus AI models are good at generating TypeScript code, benefiting from its broad usage, static typing and good linting tools)
- **Transport**: Streamable HTTP for remote servers, using stateless JSON (simpler to scale and maintain, as opposed to stateful sessions and streaming responses). stdio for local servers. - **Transport**: Streamable HTTP for remote servers, using stateless JSON (simpler to scale and maintain, as opposed to stateful sessions and streaming responses). stdio for local servers.
@@ -58,10 +60,12 @@ Key pages to review:
- **MCP Best Practices**: [📋 View Best Practices](./reference/mcp_best_practices.md) - Core guidelines - **MCP Best Practices**: [📋 View Best Practices](./reference/mcp_best_practices.md) - Core guidelines
**For TypeScript (recommended):** **For TypeScript (recommended):**
- **TypeScript SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md` - **TypeScript SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
- [⚡ TypeScript Guide](./reference/node_mcp_server.md) - TypeScript patterns and examples - [⚡ TypeScript Guide](./reference/node_mcp_server.md) - TypeScript patterns and examples
**For Python:** **For Python:**
- **Python SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md` - **Python SDK**: Use WebFetch to load `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- [🐍 Python Guide](./reference/python_mcp_server.md) - Python patterns and examples - [🐍 Python Guide](./reference/python_mcp_server.md) - Python patterns and examples
@@ -80,12 +84,14 @@ Prioritize comprehensive API coverage. List endpoints to implement, starting wit
#### 2.1 Set Up Project Structure #### 2.1 Set Up Project Structure
See language-specific guides for project setup: See language-specific guides for project setup:
- [⚡ TypeScript Guide](./reference/node_mcp_server.md) - Project structure, package.json, tsconfig.json - [⚡ TypeScript Guide](./reference/node_mcp_server.md) - Project structure, package.json, tsconfig.json
- [🐍 Python Guide](./reference/python_mcp_server.md) - Module organization, dependencies - [🐍 Python Guide](./reference/python_mcp_server.md) - Module organization, dependencies
#### 2.2 Implement Core Infrastructure #### 2.2 Implement Core Infrastructure
Create shared utilities: Create shared utilities:
- API client with authentication - API client with authentication
- Error handling helpers - Error handling helpers
- Response formatting (JSON/Markdown) - Response formatting (JSON/Markdown)
@@ -96,27 +102,32 @@ Create shared utilities:
For each tool: For each tool:
**Input Schema:** **Input Schema:**
- Use Zod (TypeScript) or Pydantic (Python) - Use Zod (TypeScript) or Pydantic (Python)
- Include constraints and clear descriptions - Include constraints and clear descriptions
- Add examples in field descriptions - Add examples in field descriptions
**Output Schema:** **Output Schema:**
- Define `outputSchema` where possible for structured data - Define `outputSchema` where possible for structured data
- Use `structuredContent` in tool responses (TypeScript SDK feature) - Use `structuredContent` in tool responses (TypeScript SDK feature)
- Helps clients understand and process tool outputs - Helps clients understand and process tool outputs
**Tool Description:** **Tool Description:**
- Concise summary of functionality - Concise summary of functionality
- Parameter descriptions - Parameter descriptions
- Return type schema - Return type schema
**Implementation:** **Implementation:**
- Async/await for I/O operations - Async/await for I/O operations
- Proper error handling with actionable messages - Proper error handling with actionable messages
- Support pagination where applicable - Support pagination where applicable
- Return both text content and structured data when using modern SDKs - Return both text content and structured data when using modern SDKs
**Annotations:** **Annotations:**
- `readOnlyHint`: true/false - `readOnlyHint`: true/false
- `destructiveHint`: true/false - `destructiveHint`: true/false
- `idempotentHint`: true/false - `idempotentHint`: true/false
@@ -129,6 +140,7 @@ For each tool:
#### 3.1 Code Quality #### 3.1 Code Quality
Review for: Review for:
- No duplicated code (DRY principle) - No duplicated code (DRY principle)
- Consistent error handling - Consistent error handling
- Full type coverage - Full type coverage
@@ -137,10 +149,12 @@ Review for:
#### 3.2 Build and Test #### 3.2 Build and Test
**TypeScript:** **TypeScript:**
- Run `npm run build` to verify compilation - Run `npm run build` to verify compilation
- Test with MCP Inspector: `npx @modelcontextprotocol/inspector` - Test with MCP Inspector: `npx @modelcontextprotocol/inspector`
**Python:** **Python:**
- Verify syntax: `python -m py_compile your_server.py` - Verify syntax: `python -m py_compile your_server.py`
- Test with MCP Inspector - Test with MCP Inspector
@@ -170,6 +184,7 @@ To create effective evaluations, follow the process outlined in the evaluation g
#### 4.3 Evaluation Requirements #### 4.3 Evaluation Requirements
Ensure each question is: Ensure each question is:
- **Independent**: Not dependent on other questions - **Independent**: Not dependent on other questions
- **Read-only**: Only non-destructive operations required - **Read-only**: Only non-destructive operations required
- **Complex**: Requiring multiple tool calls and deep exploration - **Complex**: Requiring multiple tool calls and deep exploration
@@ -200,6 +215,7 @@ Create an XML file with this structure:
Load these resources as needed during development: Load these resources as needed during development:
### Core MCP Documentation (Load First) ### Core MCP Documentation (Load First)
- **MCP Protocol**: Start with sitemap at `https://modelcontextprotocol.io/sitemap.xml`, then fetch specific pages with `.md` suffix - **MCP Protocol**: Start with sitemap at `https://modelcontextprotocol.io/sitemap.xml`, then fetch specific pages with `.md` suffix
- [📋 MCP Best Practices](./reference/mcp_best_practices.md) - Universal MCP guidelines including: - [📋 MCP Best Practices](./reference/mcp_best_practices.md) - Universal MCP guidelines including:
- Server and tool naming conventions - Server and tool naming conventions
@@ -209,10 +225,12 @@ Load these resources as needed during development:
- Security and error handling standards - Security and error handling standards
### SDK Documentation (Load During Phase 1/2) ### SDK Documentation (Load During Phase 1/2)
- **Python SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md` - **Python SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/python-sdk/main/README.md`
- **TypeScript SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md` - **TypeScript SDK**: Fetch from `https://raw.githubusercontent.com/modelcontextprotocol/typescript-sdk/main/README.md`
### Language-Specific Implementation Guides (Load During Phase 2) ### Language-Specific Implementation Guides (Load During Phase 2)
- [🐍 Python Implementation Guide](./reference/python_mcp_server.md) - Complete Python/FastMCP guide with: - [🐍 Python Implementation Guide](./reference/python_mcp_server.md) - Complete Python/FastMCP guide with:
- Server initialization patterns - Server initialization patterns
- Pydantic model examples - Pydantic model examples
@@ -228,6 +246,7 @@ Load these resources as needed during development:
- Quality checklist - Quality checklist
### Evaluation Guide (Load During Phase 4) ### Evaluation Guide (Load During Phase 4)
- [✅ Evaluation Guide](./reference/evaluation.md) - Complete evaluation creation guide with: - [✅ Evaluation Guide](./reference/evaluation.md) - Complete evaluation creation guide with:
- Question creation guidelines - Question creation guidelines
- Answer verification strategies - Answer verification strategies
@@ -9,6 +9,7 @@ This document provides guidance on creating comprehensive evaluations for MCP se
## Quick Reference ## Quick Reference
### Evaluation Requirements ### Evaluation Requirements
- Create 10 human-readable questions - Create 10 human-readable questions
- Questions must be READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE - Questions must be READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE
- Each question requires multiple tool calls (potentially dozens) - Each question requires multiple tool calls (potentially dozens)
@@ -16,6 +17,7 @@ This document provides guidance on creating comprehensive evaluations for MCP se
- Answers must be STABLE (won't change over time) - Answers must be STABLE (won't change over time)
### Output Format ### Output Format
```xml ```xml
<evaluation> <evaluation>
<qa_pair> <qa_pair>
@@ -34,6 +36,7 @@ The measure of quality of an MCP server is NOT how well or comprehensively the s
## Evaluation Overview ## Evaluation Overview
Create 10 human-readable questions requiring ONLY READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE, and IDEMPOTENT operations to answer. Each question should be: Create 10 human-readable questions requiring ONLY READ-ONLY, INDEPENDENT, NON-DESTRUCTIVE, and IDEMPOTENT operations to answer. Each question should be:
- Realistic - Realistic
- Clear and concise - Clear and concise
- Unambiguous - Unambiguous
@@ -176,6 +179,7 @@ Create 10 human-readable questions requiring ONLY READ-ONLY, INDEPENDENT, NON-DE
### Step 1: Documentation Inspection ### Step 1: Documentation Inspection
Read the documentation of the target API to understand: Read the documentation of the target API to understand:
- Available endpoints and functionality - Available endpoints and functionality
- If ambiguity exists, fetch additional information from the web - If ambiguity exists, fetch additional information from the web
- Parallelize this step AS MUCH AS POSSIBLE - Parallelize this step AS MUCH AS POSSIBLE
@@ -184,6 +188,7 @@ Read the documentation of the target API to understand:
### Step 2: Tool Inspection ### Step 2: Tool Inspection
List the tools available in the MCP server: List the tools available in the MCP server:
- Inspect the MCP server directly - Inspect the MCP server directly
- Understand input/output schemas, docstrings, and descriptions - Understand input/output schemas, docstrings, and descriptions
- WITHOUT calling the tools themselves at this stage - WITHOUT calling the tools themselves at this stage
@@ -191,6 +196,7 @@ List the tools available in the MCP server:
### Step 3: Developing Understanding ### Step 3: Developing Understanding
Repeat steps 1 & 2 until you have a good understanding: Repeat steps 1 & 2 until you have a good understanding:
- Iterate multiple times - Iterate multiple times
- Think about the kinds of tasks you want to create - Think about the kinds of tasks you want to create
- Refine your understanding - Refine your understanding
@@ -200,6 +206,7 @@ Repeat steps 1 & 2 until you have a good understanding:
### Step 4: Read-Only Content Inspection ### Step 4: Read-Only Content Inspection
After understanding the API and tools, USE the MCP server tools: After understanding the API and tools, USE the MCP server tools:
- Inspect content using READ-ONLY and NON-DESTRUCTIVE operations ONLY - Inspect content using READ-ONLY and NON-DESTRUCTIVE operations ONLY
- Goal: identify specific content (e.g., users, channels, messages, projects, tasks) for creating realistic questions - Goal: identify specific content (e.g., users, channels, messages, projects, tasks) for creating realistic questions
- Should NOT call any tools that modify state - Should NOT call any tools that modify state
@@ -214,6 +221,7 @@ After understanding the API and tools, USE the MCP server tools:
### Step 5: Task Generation ### Step 5: Task Generation
After inspecting the content, create 10 human-readable questions: After inspecting the content, create 10 human-readable questions:
- An LLM should be able to answer these with the MCP server - An LLM should be able to answer these with the MCP server
- Follow all question and answer guidelines above - Follow all question and answer guidelines above
@@ -247,6 +255,7 @@ Each QA pair consists of a question and an answer. The output should be an XML f
### Good Questions ### Good Questions
**Example 1: Multi-hop question requiring deep exploration (GitHub MCP)** **Example 1: Multi-hop question requiring deep exploration (GitHub MCP)**
```xml ```xml
<qa_pair> <qa_pair>
<question>Find the repository that was archived in Q3 2023 and had previously been the most forked project in the organization. What was the primary programming language used in that repository?</question> <question>Find the repository that was archived in Q3 2023 and had previously been the most forked project in the organization. What was the primary programming language used in that repository?</question>
@@ -255,6 +264,7 @@ Each QA pair consists of a question and an answer. The output should be an XML f
``` ```
This question is good because: This question is good because:
- Requires multiple searches to find archived repositories - Requires multiple searches to find archived repositories
- Needs to identify which had the most forks before archival - Needs to identify which had the most forks before archival
- Requires examining repository details for the language - Requires examining repository details for the language
@@ -262,6 +272,7 @@ This question is good because:
- Based on historical (closed) data that won't change - Based on historical (closed) data that won't change
**Example 2: Requires understanding context without keyword matching (Project Management MCP)** **Example 2: Requires understanding context without keyword matching (Project Management MCP)**
```xml ```xml
<qa_pair> <qa_pair>
<question>Locate the initiative focused on improving customer onboarding that was completed in late 2023. The project lead created a retrospective document after completion. What was the lead's role title at that time?</question> <question>Locate the initiative focused on improving customer onboarding that was completed in late 2023. The project lead created a retrospective document after completion. What was the lead's role title at that time?</question>
@@ -270,6 +281,7 @@ This question is good because:
``` ```
This question is good because: This question is good because:
- Doesn't use specific project name ("initiative focused on improving customer onboarding") - Doesn't use specific project name ("initiative focused on improving customer onboarding")
- Requires finding completed projects from specific timeframe - Requires finding completed projects from specific timeframe
- Needs to identify the project lead and their role - Needs to identify the project lead and their role
@@ -278,6 +290,7 @@ This question is good because:
- Based on completed work (won't change) - Based on completed work (won't change)
**Example 3: Complex aggregation requiring multiple steps (Issue Tracker MCP)** **Example 3: Complex aggregation requiring multiple steps (Issue Tracker MCP)**
```xml ```xml
<qa_pair> <qa_pair>
<question>Among all bugs reported in January 2024 that were marked as critical priority, which assignee resolved the highest percentage of their assigned bugs within 48 hours? Provide the assignee's username.</question> <question>Among all bugs reported in January 2024 that were marked as critical priority, which assignee resolved the highest percentage of their assigned bugs within 48 hours? Provide the assignee's username.</question>
@@ -286,6 +299,7 @@ This question is good because:
``` ```
This question is good because: This question is good because:
- Requires filtering bugs by date, priority, and status - Requires filtering bugs by date, priority, and status
- Needs to group by assignee and calculate resolution rates - Needs to group by assignee and calculate resolution rates
- Requires understanding timestamps to determine 48-hour windows - Requires understanding timestamps to determine 48-hour windows
@@ -294,6 +308,7 @@ This question is good because:
- Based on historical data from specific time period - Based on historical data from specific time period
**Example 4: Requires synthesis across multiple data types (CRM MCP)** **Example 4: Requires synthesis across multiple data types (CRM MCP)**
```xml ```xml
<qa_pair> <qa_pair>
<question>Find the account that upgraded from the Starter to Enterprise plan in Q4 2023 and had the highest annual contract value. What industry does this account operate in?</question> <question>Find the account that upgraded from the Starter to Enterprise plan in Q4 2023 and had the highest annual contract value. What industry does this account operate in?</question>
@@ -302,6 +317,7 @@ This question is good because:
``` ```
This question is good because: This question is good because:
- Requires understanding subscription tier changes - Requires understanding subscription tier changes
- Needs to identify upgrade events in specific timeframe - Needs to identify upgrade events in specific timeframe
- Requires comparing contract values - Requires comparing contract values
@@ -312,6 +328,7 @@ This question is good because:
### Poor Questions ### Poor Questions
**Example 1: Answer changes over time** **Example 1: Answer changes over time**
```xml ```xml
<qa_pair> <qa_pair>
<question>How many open issues are currently assigned to the engineering team?</question> <question>How many open issues are currently assigned to the engineering team?</question>
@@ -320,11 +337,13 @@ This question is good because:
``` ```
This question is poor because: This question is poor because:
- The answer will change as issues are created, closed, or reassigned - The answer will change as issues are created, closed, or reassigned
- Not based on stable/stationary data - Not based on stable/stationary data
- Relies on "current state" which is dynamic - Relies on "current state" which is dynamic
**Example 2: Too easy with keyword search** **Example 2: Too easy with keyword search**
```xml ```xml
<qa_pair> <qa_pair>
<question>Find the pull request with title "Add authentication feature" and tell me who created it.</question> <question>Find the pull request with title "Add authentication feature" and tell me who created it.</question>
@@ -333,11 +352,13 @@ This question is poor because:
``` ```
This question is poor because: This question is poor because:
- Can be solved with a straightforward keyword search for exact title - Can be solved with a straightforward keyword search for exact title
- Doesn't require deep exploration or understanding - Doesn't require deep exploration or understanding
- No synthesis or analysis needed - No synthesis or analysis needed
**Example 3: Ambiguous answer format** **Example 3: Ambiguous answer format**
```xml ```xml
<qa_pair> <qa_pair>
<question>List all the repositories that have Python as their primary language.</question> <question>List all the repositories that have Python as their primary language.</question>
@@ -346,6 +367,7 @@ This question is poor because:
``` ```
This question is poor because: This question is poor because:
- Answer is a list that could be returned in any order - Answer is a list that could be returned in any order
- Difficult to verify with direct string comparison - Difficult to verify with direct string comparison
- LLM might format differently (JSON array, comma-separated, newline-separated) - LLM might format differently (JSON array, comma-separated, newline-separated)
@@ -388,6 +410,7 @@ After creating your evaluation file, you can use the provided evaluation harness
``` ```
Or install manually: Or install manually:
```bash ```bash
pip install anthropic mcp pip install anthropic mcp
``` ```
@@ -420,6 +443,7 @@ Evaluation files use XML format with `<qa_pair>` elements:
The evaluation script (`scripts/evaluation.py`) supports three transport types: The evaluation script (`scripts/evaluation.py`) supports three transport types:
**Important:** **Important:**
- **stdio transport**: The evaluation script automatically launches and manages the MCP server process for you. Do not run the server manually. - **stdio transport**: The evaluation script automatically launches and manages the MCP server process for you. Do not run the server manually.
- **sse/http transports**: You must start the MCP server separately before running the evaluation. The script connects to the already-running server at the specified URL. - **sse/http transports**: You must start the MCP server separately before running the evaluation. The script connects to the already-running server at the specified URL.
@@ -436,6 +460,7 @@ python scripts/evaluation.py \
``` ```
With environment variables: With environment variables:
```bash ```bash
python scripts/evaluation.py \ python scripts/evaluation.py \
-t stdio \ -t stdio \
@@ -580,6 +605,7 @@ python scripts/evaluation.py \
### Connection Errors ### Connection Errors
If you get connection errors: If you get connection errors:
- **STDIO**: Verify the command and arguments are correct - **STDIO**: Verify the command and arguments are correct
- **SSE/HTTP**: Check the URL is accessible and headers are correct - **SSE/HTTP**: Check the URL is accessible and headers are correct
- Ensure any required API keys are set in environment variables or headers - Ensure any required API keys are set in environment variables or headers
@@ -587,6 +613,7 @@ If you get connection errors:
### Low Accuracy ### Low Accuracy
If many evaluations fail: If many evaluations fail:
- Review the agent's feedback for each task - Review the agent's feedback for each task
- Check if tool descriptions are clear and comprehensive - Check if tool descriptions are clear and comprehensive
- Verify input parameters are well-documented - Verify input parameters are well-documented
@@ -596,7 +623,8 @@ If many evaluations fail:
### Timeout Issues ### Timeout Issues
If tasks are timing out: If tasks are timing out:
- Use a more capable model (e.g., `claude-3-7-sonnet-20250219`) - Use a more capable model (e.g., `claude-3-7-sonnet-20250219`)
- Check if tools are returning too much data - Check if tools are returning too much data
- Verify pagination is working correctly - Verify pagination is working correctly
- Consider simplifying complex questions - Consider simplifying complex questions
@@ -3,25 +3,30 @@
## Quick Reference ## Quick Reference
### Server Naming ### Server Naming
- **Python**: `{service}_mcp` (e.g., `slack_mcp`) - **Python**: `{service}_mcp` (e.g., `slack_mcp`)
- **Node/TypeScript**: `{service}-mcp-server` (e.g., `slack-mcp-server`) - **Node/TypeScript**: `{service}-mcp-server` (e.g., `slack-mcp-server`)
### Tool Naming ### Tool Naming
- Use snake_case with service prefix - Use snake_case with service prefix
- Format: `{service}_{action}_{resource}` - Format: `{service}_{action}_{resource}`
- Example: `slack_send_message`, `github_create_issue` - Example: `slack_send_message`, `github_create_issue`
### Response Formats ### Response Formats
- Support both JSON and Markdown formats - Support both JSON and Markdown formats
- JSON for programmatic processing - JSON for programmatic processing
- Markdown for human readability - Markdown for human readability
### Pagination ### Pagination
- Always respect `limit` parameter - Always respect `limit` parameter
- Return `has_more`, `next_offset`, `total_count` - Return `has_more`, `next_offset`, `total_count`
- Default to 20-50 items - Default to 20-50 items
### Transport ### Transport
- **Streamable HTTP**: For remote servers, multi-client scenarios - **Streamable HTTP**: For remote servers, multi-client scenarios
- **stdio**: For local integrations, command-line tools - **stdio**: For local integrations, command-line tools
- Avoid SSE (deprecated in favor of streamable HTTP) - Avoid SSE (deprecated in favor of streamable HTTP)
@@ -33,9 +38,11 @@
Follow these standardized naming patterns: Follow these standardized naming patterns:
**Python**: Use format `{service}_mcp` (lowercase with underscores) **Python**: Use format `{service}_mcp` (lowercase with underscores)
- Examples: `slack_mcp`, `github_mcp`, `jira_mcp` - Examples: `slack_mcp`, `github_mcp`, `jira_mcp`
**Node/TypeScript**: Use format `{service}-mcp-server` (lowercase with hyphens) **Node/TypeScript**: Use format `{service}-mcp-server` (lowercase with hyphens)
- Examples: `slack-mcp-server`, `github-mcp-server`, `jira-mcp-server` - Examples: `slack-mcp-server`, `github-mcp-server`, `jira-mcp-server`
The name should be general, descriptive of the service being integrated, easy to infer from the task description, and without version numbers. The name should be general, descriptive of the service being integrated, easy to infer from the task description, and without version numbers.
@@ -67,12 +74,14 @@ The name should be general, descriptive of the service being integrated, easy to
All tools that return data should support multiple formats: All tools that return data should support multiple formats:
### JSON Format (`response_format="json"`) ### JSON Format (`response_format="json"`)
- Machine-readable structured data - Machine-readable structured data
- Include all available fields and metadata - Include all available fields and metadata
- Consistent field names and types - Consistent field names and types
- Use for programmatic processing - Use for programmatic processing
### Markdown Format (`response_format="markdown"`, typically default) ### Markdown Format (`response_format="markdown"`, typically default)
- Human-readable formatted text - Human-readable formatted text
- Use headers, lists, and formatting for clarity - Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format - Convert timestamps to human-readable format
@@ -92,6 +101,7 @@ For tools that list resources:
- **Default to reasonable limits**: 20-50 items is typical - **Default to reasonable limits**: 20-50 items is typical
Example pagination response: Example pagination response:
```json ```json
{ {
"total": 150, "total": 150,
@@ -112,12 +122,14 @@ Example pagination response:
**Best for**: Remote servers, web services, multi-client scenarios **Best for**: Remote servers, web services, multi-client scenarios
**Characteristics**: **Characteristics**:
- Bidirectional communication over HTTP - Bidirectional communication over HTTP
- Supports multiple simultaneous clients - Supports multiple simultaneous clients
- Can be deployed as a web service - Can be deployed as a web service
- Enables server-to-client notifications - Enables server-to-client notifications
**Use when**: **Use when**:
- Serving multiple clients simultaneously - Serving multiple clients simultaneously
- Deploying as a cloud service - Deploying as a cloud service
- Integration with web applications - Integration with web applications
@@ -127,11 +139,13 @@ Example pagination response:
**Best for**: Local integrations, command-line tools **Best for**: Local integrations, command-line tools
**Characteristics**: **Characteristics**:
- Standard input/output stream communication - Standard input/output stream communication
- Simple setup, no network configuration needed - Simple setup, no network configuration needed
- Runs as a subprocess of the client - Runs as a subprocess of the client
**Use when**: **Use when**:
- Building tools for local development environments - Building tools for local development environments
- Integrating with desktop applications - Integrating with desktop applications
- Single-user, single-session scenarios - Single-user, single-session scenarios
@@ -140,12 +154,12 @@ Example pagination response:
### Transport Selection ### Transport Selection
| Criterion | stdio | Streamable HTTP | | Criterion | stdio | Streamable HTTP |
|-----------|-------|-----------------| | -------------- | ------ | --------------- |
| **Deployment** | Local | Remote | | **Deployment** | Local | Remote |
| **Clients** | Single | Multiple | | **Clients** | Single | Multiple |
| **Complexity** | Low | Medium | | **Complexity** | Low | Medium |
| **Real-time** | No | Yes | | **Real-time** | No | Yes |
--- ---
@@ -154,11 +168,13 @@ Example pagination response:
### Authentication and Authorization ### Authentication and Authorization
**OAuth 2.1**: **OAuth 2.1**:
- Use secure OAuth 2.1 with certificates from recognized authorities - Use secure OAuth 2.1 with certificates from recognized authorities
- Validate access tokens before processing requests - Validate access tokens before processing requests
- Only accept tokens specifically intended for your server - Only accept tokens specifically intended for your server
**API Keys**: **API Keys**:
- Store API keys in environment variables, never in code - Store API keys in environment variables, never in code
- Validate keys on server startup - Validate keys on server startup
- Provide clear error messages when authentication fails - Provide clear error messages when authentication fails
@@ -181,6 +197,7 @@ Example pagination response:
### DNS Rebinding Protection ### DNS Rebinding Protection
For streamable HTTP servers running locally: For streamable HTTP servers running locally:
- Enable DNS rebinding protection - Enable DNS rebinding protection
- Validate the `Origin` header on all incoming connections - Validate the `Origin` header on all incoming connections
- Bind to `127.0.0.1` rather than `0.0.0.0` - Bind to `127.0.0.1` rather than `0.0.0.0`
@@ -191,12 +208,12 @@ For streamable HTTP servers running locally:
Provide annotations to help clients understand tool behavior: Provide annotations to help clients understand tool behavior:
| Annotation | Type | Default | Description | | Annotation | Type | Default | Description |
|-----------|------|---------|-------------| | ----------------- | ------- | ------- | ------------------------------------------------------- |
| `readOnlyHint` | boolean | false | Tool does not modify its environment | | `readOnlyHint` | boolean | false | Tool does not modify its environment |
| `destructiveHint` | boolean | true | Tool may perform destructive updates | | `destructiveHint` | boolean | true | Tool may perform destructive updates |
| `idempotentHint` | boolean | false | Repeated calls with same args have no additional effect | | `idempotentHint` | boolean | false | Repeated calls with same args have no additional effect |
| `openWorldHint` | boolean | true | Tool interacts with external entities | | `openWorldHint` | boolean | true | Tool interacts with external entities |
**Important**: Annotations are hints, not security guarantees. Clients should not make security-critical decisions based solely on annotations. **Important**: Annotations are hints, not security guarantees. Clients should not make security-critical decisions based solely on annotations.
@@ -211,17 +228,20 @@ Provide annotations to help clients understand tool behavior:
- Clean up resources properly on errors - Clean up resources properly on errors
Example error handling: Example error handling:
```typescript ```typescript
try { try {
const result = performOperation(); const result = performOperation();
return { content: [{ type: "text", text: result }] }; return { content: [{ type: 'text', text: result }] };
} catch (error) { } catch (error) {
return { return {
isError: true, isError: true,
content: [{ content: [
type: "text", {
text: `Error: ${error.message}. Try using filter='active_only' to reduce results.` type: 'text',
}] text: `Error: ${error.message}. Try using filter='active_only' to reduce results.`,
},
],
}; };
} }
``` ```
@@ -9,39 +9,42 @@ This document provides Node/TypeScript-specific best practices and examples for
## Quick Reference ## Quick Reference
### Key Imports ### Key Imports
```typescript ```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from "express"; import express from 'express';
import { z } from "zod"; import { z } from 'zod';
``` ```
### Server Initialization ### Server Initialization
```typescript ```typescript
const server = new McpServer({ const server = new McpServer({
name: "service-mcp-server", name: 'service-mcp-server',
version: "1.0.0" version: '1.0.0',
}); });
``` ```
### Tool Registration Pattern ### Tool Registration Pattern
```typescript ```typescript
server.registerTool( server.registerTool(
"tool_name", 'tool_name',
{ {
title: "Tool Display Name", title: 'Tool Display Name',
description: "What the tool does", description: 'What the tool does',
inputSchema: { param: z.string() }, inputSchema: { param: z.string() },
outputSchema: { result: z.string() } outputSchema: { result: z.string() },
}, },
async ({ param }) => { async ({ param }) => {
const output = { result: `Processed: ${param}` }; const output = { result: `Processed: ${param}` };
return { return {
content: [{ type: "text", text: JSON.stringify(output) }], content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output // Modern pattern for structured data structuredContent: output, // Modern pattern for structured data
}; };
} },
); );
``` ```
@@ -50,12 +53,14 @@ server.registerTool(
## MCP TypeScript SDK ## MCP TypeScript SDK
The official MCP TypeScript SDK provides: The official MCP TypeScript SDK provides:
- `McpServer` class for server initialization - `McpServer` class for server initialization
- `registerTool` method for tool registration - `registerTool` method for tool registration
- Zod schema integration for runtime input validation - Zod schema integration for runtime input validation
- Type-safe tool handler implementations - Type-safe tool handler implementations
**IMPORTANT - Use Modern APIs Only:** **IMPORTANT - Use Modern APIs Only:**
- **DO use**: `server.registerTool()`, `server.registerResource()`, `server.registerPrompt()` - **DO use**: `server.registerTool()`, `server.registerResource()`, `server.registerPrompt()`
- **DO NOT use**: Old deprecated APIs such as `server.tool()`, `server.setRequestHandler(ListToolsRequestSchema, ...)`, or manual handler registration - **DO NOT use**: Old deprecated APIs such as `server.tool()`, `server.setRequestHandler(ListToolsRequestSchema, ...)`, or manual handler registration
- The `register*` methods provide better type safety, automatic schema handling, and are the recommended approach - The `register*` methods provide better type safety, automatic schema handling, and are the recommended approach
@@ -65,10 +70,12 @@ See the MCP SDK documentation in the references for complete details.
## Server Naming Convention ## Server Naming Convention
Node/TypeScript MCP servers must follow this naming pattern: Node/TypeScript MCP servers must follow this naming pattern:
- **Format**: `{service}-mcp-server` (lowercase with hyphens) - **Format**: `{service}-mcp-server` (lowercase with hyphens)
- **Examples**: `github-mcp-server`, `jira-mcp-server`, `stripe-mcp-server` - **Examples**: `github-mcp-server`, `jira-mcp-server`, `stripe-mcp-server`
The name should be: The name should be:
- General (not tied to specific features) - General (not tied to specific features)
- Descriptive of the service/API being integrated - Descriptive of the service/API being integrated
- Easy to infer from the task description - Easy to infer from the task description
@@ -100,6 +107,7 @@ Create the following structure for Node/TypeScript MCP servers:
Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names. Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names.
**Avoid Naming Conflicts**: Include the service context to prevent overlaps: **Avoid Naming Conflicts**: Include the service context to prevent overlaps:
- Use "slack_send_message" instead of just "send_message" - Use "slack_send_message" instead of just "send_message"
- Use "github_create_issue" instead of just "create_issue" - Use "github_create_issue" instead of just "create_issue"
- Use "asana_list_tasks" instead of just "list_tasks" - Use "asana_list_tasks" instead of just "list_tasks"
@@ -107,6 +115,7 @@ Use snake_case for tool names (e.g., "search_users", "create_project", "get_chan
### Tool Structure ### Tool Structure
Tools are registered using the `registerTool` method with the following requirements: Tools are registered using the `registerTool` method with the following requirements:
- Use Zod schemas for runtime input validation and type safety - Use Zod schemas for runtime input validation and type safety
- The `description` field must be explicitly provided - JSDoc comments are NOT automatically extracted - The `description` field must be explicitly provided - JSDoc comments are NOT automatically extracted
- Explicitly provide `title`, `description`, `inputSchema`, and `annotations` - Explicitly provide `title`, `description`, `inputSchema`, and `annotations`
@@ -114,43 +123,38 @@ Tools are registered using the `registerTool` method with the following requirem
- Type all parameters and return values explicitly - Type all parameters and return values explicitly
```typescript ```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from "zod"; import { z } from 'zod';
const server = new McpServer({ const server = new McpServer({
name: "example-mcp", name: 'example-mcp',
version: "1.0.0" version: '1.0.0',
}); });
// Zod schema for input validation // Zod schema for input validation
const UserSearchInputSchema = z.object({ const UserSearchInputSchema = z
query: z.string() .object({
.min(2, "Query must be at least 2 characters") query: z
.max(200, "Query must not exceed 200 characters") .string()
.describe("Search string to match against names/emails"), .min(2, 'Query must be at least 2 characters')
limit: z.number() .max(200, 'Query must not exceed 200 characters')
.int() .describe('Search string to match against names/emails'),
.min(1) limit: z.number().int().min(1).max(100).default(20).describe('Maximum results to return'),
.max(100) offset: z.number().int().min(0).default(0).describe('Number of results to skip for pagination'),
.default(20) response_format: z
.describe("Maximum results to return"), .nativeEnum(ResponseFormat)
offset: z.number() .default(ResponseFormat.MARKDOWN)
.int() .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"),
.min(0) })
.default(0) .strict();
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
// Type definition from Zod schema // Type definition from Zod schema
type UserSearchInput = z.infer<typeof UserSearchInputSchema>; type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
server.registerTool( server.registerTool(
"example_search_users", 'example_search_users',
{ {
title: "Search Example Users", title: 'Search Example Users',
description: `Search for users in the Example system by name, email, or team. description: `Search for users in the Example system by name, email, or team.
This tool searches across all user profiles in the Example platform, supporting partial matches and various search filters. It does NOT create or modify users, only searches existing ones. This tool searches across all user profiles in the Example platform, supporting partial matches and various search filters. It does NOT create or modify users, only searches existing ones.
@@ -193,33 +197,30 @@ Error Handling:
readOnlyHint: true, readOnlyHint: true,
destructiveHint: false, destructiveHint: false,
idempotentHint: true, idempotentHint: true,
openWorldHint: true openWorldHint: true,
} },
}, },
async (params: UserSearchInput) => { async (params: UserSearchInput) => {
try { try {
// Input validation is handled by Zod schema // Input validation is handled by Zod schema
// Make API request using validated parameters // Make API request using validated parameters
const data = await makeApiRequest<any>( const data = await makeApiRequest<any>('users/search', 'GET', undefined, {
"users/search", q: params.query,
"GET", limit: params.limit,
undefined, offset: params.offset,
{ });
q: params.query,
limit: params.limit,
offset: params.offset
}
);
const users = data.users || []; const users = data.users || [];
const total = data.total || 0; const total = data.total || 0;
if (!users.length) { if (!users.length) {
return { return {
content: [{ content: [
type: "text", {
text: `No users found matching '${params.query}'` type: 'text',
}] text: `No users found matching '${params.query}'`,
},
],
}; };
} }
@@ -233,43 +234,51 @@ Error Handling:
name: user.name, name: user.name,
email: user.email, email: user.email,
...(user.team ? { team: user.team } : {}), ...(user.team ? { team: user.team } : {}),
active: user.active ?? true active: user.active ?? true,
})), })),
has_more: total > params.offset + users.length, has_more: total > params.offset + users.length,
...(total > params.offset + users.length ? { ...(total > params.offset + users.length
next_offset: params.offset + users.length ? {
} : {}) next_offset: params.offset + users.length,
}
: {}),
}; };
// Format text representation based on requested format // Format text representation based on requested format
let textContent: string; let textContent: string;
if (params.response_format === ResponseFormat.MARKDOWN) { if (params.response_format === ResponseFormat.MARKDOWN) {
const lines = [`# User Search Results: '${params.query}'`, "", const lines = [
`Found ${total} users (showing ${users.length})`, ""]; `# User Search Results: '${params.query}'`,
'',
`Found ${total} users (showing ${users.length})`,
'',
];
for (const user of users) { for (const user of users) {
lines.push(`## ${user.name} (${user.id})`); lines.push(`## ${user.name} (${user.id})`);
lines.push(`- **Email**: ${user.email}`); lines.push(`- **Email**: ${user.email}`);
if (user.team) lines.push(`- **Team**: ${user.team}`); if (user.team) lines.push(`- **Team**: ${user.team}`);
lines.push(""); lines.push('');
} }
textContent = lines.join("\n"); textContent = lines.join('\n');
} else { } else {
textContent = JSON.stringify(output, null, 2); textContent = JSON.stringify(output, null, 2);
} }
return { return {
content: [{ type: "text", text: textContent }], content: [{ type: 'text', text: textContent }],
structuredContent: output // Modern pattern for structured data structuredContent: output, // Modern pattern for structured data
}; };
} catch (error) { } catch (error) {
return { return {
content: [{ content: [
type: "text", {
text: handleApiError(error) type: 'text',
}] text: handleApiError(error),
},
],
}; };
} }
} },
); );
``` ```
@@ -278,46 +287,38 @@ Error Handling:
Zod provides runtime type validation: Zod provides runtime type validation:
```typescript ```typescript
import { z } from "zod"; import { z } from 'zod';
// Basic schema with validation // Basic schema with validation
const CreateUserSchema = z.object({ const CreateUserSchema = z
name: z.string() .object({
.min(1, "Name is required") name: z.string().min(1, 'Name is required').max(100, 'Name must not exceed 100 characters'),
.max(100, "Name must not exceed 100 characters"), email: z.string().email('Invalid email format'),
email: z.string() age: z
.email("Invalid email format"), .number()
age: z.number() .int('Age must be a whole number')
.int("Age must be a whole number") .min(0, 'Age cannot be negative')
.min(0, "Age cannot be negative") .max(150, 'Age cannot be greater than 150'),
.max(150, "Age cannot be greater than 150") })
}).strict(); // Use .strict() to forbid extra fields .strict(); // Use .strict() to forbid extra fields
// Enums // Enums
enum ResponseFormat { enum ResponseFormat {
MARKDOWN = "markdown", MARKDOWN = 'markdown',
JSON = "json" JSON = 'json',
} }
const SearchSchema = z.object({ const SearchSchema = z.object({
response_format: z.nativeEnum(ResponseFormat) response_format: z
.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN) .default(ResponseFormat.MARKDOWN)
.describe("Output format") .describe('Output format'),
}); });
// Optional fields with defaults // Optional fields with defaults
const PaginationSchema = z.object({ const PaginationSchema = z.object({
limit: z.number() limit: z.number().int().min(1).max(100).default(20).describe('Maximum results to return'),
.int() offset: z.number().int().min(0).default(0).describe('Number of results to skip'),
.min(1)
.max(100)
.default(20)
.describe("Maximum results to return"),
offset: z.number()
.int()
.min(0)
.default(0)
.describe("Number of results to skip")
}); });
``` ```
@@ -327,19 +328,21 @@ Support multiple output formats for flexibility:
```typescript ```typescript
enum ResponseFormat { enum ResponseFormat {
MARKDOWN = "markdown", MARKDOWN = 'markdown',
JSON = "json" JSON = 'json',
} }
const inputSchema = z.object({ const inputSchema = z.object({
query: z.string(), query: z.string(),
response_format: z.nativeEnum(ResponseFormat) response_format: z
.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN) .default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable") .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"),
}); });
``` ```
**Markdown format**: **Markdown format**:
- Use headers, lists, and formatting for clarity - Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format - Convert timestamps to human-readable format
- Show display names with IDs in parentheses - Show display names with IDs in parentheses
@@ -347,6 +350,7 @@ const inputSchema = z.object({
- Group related information logically - Group related information logically
**JSON format**: **JSON format**:
- Return complete, structured data suitable for programmatic processing - Return complete, structured data suitable for programmatic processing
- Include all available fields and metadata - Include all available fields and metadata
- Use consistent field names and types - Use consistent field names and types
@@ -358,7 +362,7 @@ For tools that list resources:
```typescript ```typescript
const ListSchema = z.object({ const ListSchema = z.object({
limit: z.number().int().min(1).max(100).default(20), limit: z.number().int().min(1).max(100).default(20),
offset: z.number().int().min(0).default(0) offset: z.number().int().min(0).default(0),
}); });
async function listItems(params: z.infer<typeof ListSchema>) { async function listItems(params: z.infer<typeof ListSchema>) {
@@ -370,9 +374,10 @@ async function listItems(params: z.infer<typeof ListSchema>) {
offset: params.offset, offset: params.offset,
items: data.items, items: data.items,
has_more: data.total > params.offset + data.items.length, has_more: data.total > params.offset + data.items.length,
next_offset: data.total > params.offset + data.items.length next_offset:
? params.offset + data.items.length data.total > params.offset + data.items.length
: undefined ? params.offset + data.items.length
: undefined,
}; };
return JSON.stringify(response, null, 2); return JSON.stringify(response, null, 2);
@@ -385,7 +390,7 @@ Add a CHARACTER_LIMIT constant to prevent overwhelming responses:
```typescript ```typescript
// At module level in constants.ts // At module level in constants.ts
export const CHARACTER_LIMIT = 25000; // Maximum response size in characters export const CHARACTER_LIMIT = 25000; // Maximum response size in characters
async function searchTool(params: SearchInput) { async function searchTool(params: SearchInput) {
let result = generateResponse(data); let result = generateResponse(data);
@@ -410,23 +415,23 @@ async function searchTool(params: SearchInput) {
Provide clear, actionable error messages: Provide clear, actionable error messages:
```typescript ```typescript
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from 'axios';
function handleApiError(error: unknown): string { function handleApiError(error: unknown): string {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
if (error.response) { if (error.response) {
switch (error.response.status) { switch (error.response.status) {
case 404: case 404:
return "Error: Resource not found. Please check the ID is correct."; return 'Error: Resource not found. Please check the ID is correct.';
case 403: case 403:
return "Error: Permission denied. You don't have access to this resource."; return "Error: Permission denied. You don't have access to this resource.";
case 429: case 429:
return "Error: Rate limit exceeded. Please wait before making more requests."; return 'Error: Rate limit exceeded. Please wait before making more requests.';
default: default:
return `Error: API request failed with status ${error.response.status}`; return `Error: API request failed with status ${error.response.status}`;
} }
} else if (error.code === "ECONNABORTED") { } else if (error.code === 'ECONNABORTED') {
return "Error: Request timed out. Please try again."; return 'Error: Request timed out. Please try again.';
} }
} }
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`; return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
@@ -441,9 +446,9 @@ Extract common functionality into reusable functions:
// Shared API request function // Shared API request function
async function makeApiRequest<T>( async function makeApiRequest<T>(
endpoint: string, endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET", method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: any, data?: any,
params?: any params?: any,
): Promise<T> { ): Promise<T> {
try { try {
const response = await axios({ const response = await axios({
@@ -453,9 +458,9 @@ async function makeApiRequest<T>(
params, params,
timeout: 30000, timeout: 30000,
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"Accept": "application/json" Accept: 'application/json',
} },
}); });
return response.data; return response.data;
} catch (error) { } catch (error) {
@@ -477,8 +482,7 @@ async function fetchData(resourceId: string): Promise<ResourceData> {
// Bad: Promise chains // Bad: Promise chains
function fetchData(resourceId: string): Promise<ResourceData> { function fetchData(resourceId: string): Promise<ResourceData> {
return axios.get(`${API_URL}/resource/${resourceId}`) return axios.get(`${API_URL}/resource/${resourceId}`).then((response) => response.data); // Harder to read and maintain
.then(response => response.data); // Harder to read and maintain
} }
``` ```
@@ -507,19 +511,19 @@ const UserSchema = z.object({
name: z.string(), name: z.string(),
email: z.string().email(), email: z.string().email(),
team: z.string().optional(), team: z.string().optional(),
active: z.boolean() active: z.boolean(),
}); });
type User = z.infer<typeof UserSchema>; type User = z.infer<typeof UserSchema>;
async function getUser(id: string): Promise<User> { async function getUser(id: string): Promise<User> {
const data = await apiCall(`/users/${id}`); const data = await apiCall(`/users/${id}`);
return UserSchema.parse(data); // Runtime validation return UserSchema.parse(data); // Runtime validation
} }
// Bad: Using any // Bad: Using any
async function getUser(id: string): Promise<any> { async function getUser(id: string): Promise<any> {
return await apiCall(`/users/${id}`); // No type safety return await apiCall(`/users/${id}`); // No type safety
} }
``` ```
@@ -592,51 +596,46 @@ async function getUser(id: string): Promise<any> {
* project management, and data export capabilities. * project management, and data export capabilities.
*/ */
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from "zod"; import { z } from 'zod';
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from 'axios';
// Constants // Constants
const API_BASE_URL = "https://api.example.com/v1"; const API_BASE_URL = 'https://api.example.com/v1';
const CHARACTER_LIMIT = 25000; const CHARACTER_LIMIT = 25000;
// Enums // Enums
enum ResponseFormat { enum ResponseFormat {
MARKDOWN = "markdown", MARKDOWN = 'markdown',
JSON = "json" JSON = 'json',
} }
// Zod schemas // Zod schemas
const UserSearchInputSchema = z.object({ const UserSearchInputSchema = z
query: z.string() .object({
.min(2, "Query must be at least 2 characters") query: z
.max(200, "Query must not exceed 200 characters") .string()
.describe("Search string to match against names/emails"), .min(2, 'Query must be at least 2 characters')
limit: z.number() .max(200, 'Query must not exceed 200 characters')
.int() .describe('Search string to match against names/emails'),
.min(1) limit: z.number().int().min(1).max(100).default(20).describe('Maximum results to return'),
.max(100) offset: z.number().int().min(0).default(0).describe('Number of results to skip for pagination'),
.default(20) response_format: z
.describe("Maximum results to return"), .nativeEnum(ResponseFormat)
offset: z.number() .default(ResponseFormat.MARKDOWN)
.int() .describe("Output format: 'markdown' for human-readable or 'json' for machine-readable"),
.min(0) })
.default(0) .strict();
.describe("Number of results to skip for pagination"),
response_format: z.nativeEnum(ResponseFormat)
.default(ResponseFormat.MARKDOWN)
.describe("Output format: 'markdown' for human-readable or 'json' for machine-readable")
}).strict();
type UserSearchInput = z.infer<typeof UserSearchInputSchema>; type UserSearchInput = z.infer<typeof UserSearchInputSchema>;
// Shared utility functions // Shared utility functions
async function makeApiRequest<T>( async function makeApiRequest<T>(
endpoint: string, endpoint: string,
method: "GET" | "POST" | "PUT" | "DELETE" = "GET", method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
data?: any, data?: any,
params?: any params?: any,
): Promise<T> { ): Promise<T> {
try { try {
const response = await axios({ const response = await axios({
@@ -646,9 +645,9 @@ async function makeApiRequest<T>(
params, params,
timeout: 30000, timeout: 30000,
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
"Accept": "application/json" Accept: 'application/json',
} },
}); });
return response.data; return response.data;
} catch (error) { } catch (error) {
@@ -661,16 +660,16 @@ function handleApiError(error: unknown): string {
if (error.response) { if (error.response) {
switch (error.response.status) { switch (error.response.status) {
case 404: case 404:
return "Error: Resource not found. Please check the ID is correct."; return 'Error: Resource not found. Please check the ID is correct.';
case 403: case 403:
return "Error: Permission denied. You don't have access to this resource."; return "Error: Permission denied. You don't have access to this resource.";
case 429: case 429:
return "Error: Rate limit exceeded. Please wait before making more requests."; return 'Error: Rate limit exceeded. Please wait before making more requests.';
default: default:
return `Error: API request failed with status ${error.response.status}`; return `Error: API request failed with status ${error.response.status}`;
} }
} else if (error.code === "ECONNABORTED") { } else if (error.code === 'ECONNABORTED') {
return "Error: Request timed out. Please try again."; return 'Error: Request timed out. Please try again.';
} }
} }
return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`; return `Error: Unexpected error occurred: ${error instanceof Error ? error.message : String(error)}`;
@@ -678,46 +677,46 @@ function handleApiError(error: unknown): string {
// Create MCP server instance // Create MCP server instance
const server = new McpServer({ const server = new McpServer({
name: "example-mcp", name: 'example-mcp',
version: "1.0.0" version: '1.0.0',
}); });
// Register tools // Register tools
server.registerTool( server.registerTool(
"example_search_users", 'example_search_users',
{ {
title: "Search Example Users", title: 'Search Example Users',
description: `[Full description as shown above]`, description: `[Full description as shown above]`,
inputSchema: UserSearchInputSchema, inputSchema: UserSearchInputSchema,
annotations: { annotations: {
readOnlyHint: true, readOnlyHint: true,
destructiveHint: false, destructiveHint: false,
idempotentHint: true, idempotentHint: true,
openWorldHint: true openWorldHint: true,
} },
}, },
async (params: UserSearchInput) => { async (params: UserSearchInput) => {
// Implementation as shown above // Implementation as shown above
} },
); );
// Main function // Main function
// For stdio (local): // For stdio (local):
async function runStdio() { async function runStdio() {
if (!process.env.EXAMPLE_API_KEY) { if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required"); console.error('ERROR: EXAMPLE_API_KEY environment variable is required');
process.exit(1); process.exit(1);
} }
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);
console.error("MCP server running via stdio"); console.error('MCP server running via stdio');
} }
// For streamable HTTP (remote): // For streamable HTTP (remote):
async function runHTTP() { async function runHTTP() {
if (!process.env.EXAMPLE_API_KEY) { if (!process.env.EXAMPLE_API_KEY) {
console.error("ERROR: EXAMPLE_API_KEY environment variable is required"); console.error('ERROR: EXAMPLE_API_KEY environment variable is required');
process.exit(1); process.exit(1);
} }
@@ -727,7 +726,7 @@ async function runHTTP() {
app.post('/mcp', async (req, res) => { app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({ const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, sessionIdGenerator: undefined,
enableJsonResponse: true enableJsonResponse: true,
}); });
res.on('close', () => transport.close()); res.on('close', () => transport.close());
await server.connect(transport); await server.connect(transport);
@@ -743,13 +742,13 @@ async function runHTTP() {
// Choose transport based on environment // Choose transport based on environment
const transport = process.env.TRANSPORT || 'stdio'; const transport = process.env.TRANSPORT || 'stdio';
if (transport === 'http') { if (transport === 'http') {
runHTTP().catch(error => { runHTTP().catch((error) => {
console.error("Server error:", error); console.error('Server error:', error);
process.exit(1); process.exit(1);
}); });
} else { } else {
runStdio().catch(error => { runStdio().catch((error) => {
console.error("Server error:", error); console.error('Server error:', error);
process.exit(1); process.exit(1);
}); });
} }
@@ -764,51 +763,54 @@ if (transport === 'http') {
Expose data as resources for efficient, URI-based access: Expose data as resources for efficient, URI-based access:
```typescript ```typescript
import { ResourceTemplate } from "@modelcontextprotocol/sdk/types.js"; import { ResourceTemplate } from '@modelcontextprotocol/sdk/types.js';
// Register a resource with URI template // Register a resource with URI template
server.registerResource( server.registerResource(
{ {
uri: "file://documents/{name}", uri: 'file://documents/{name}',
name: "Document Resource", name: 'Document Resource',
description: "Access documents by name", description: 'Access documents by name',
mimeType: "text/plain" mimeType: 'text/plain',
}, },
async (uri: string) => { async (uri: string) => {
// Extract parameter from URI // Extract parameter from URI
const match = uri.match(/^file:\/\/documents\/(.+)$/); const match = uri.match(/^file:\/\/documents\/(.+)$/);
if (!match) { if (!match) {
throw new Error("Invalid URI format"); throw new Error('Invalid URI format');
} }
const documentName = match[1]; const documentName = match[1];
const content = await loadDocument(documentName); const content = await loadDocument(documentName);
return { return {
contents: [{ contents: [
uri, {
mimeType: "text/plain", uri,
text: content mimeType: 'text/plain',
}] text: content,
},
],
}; };
} },
); );
// List available resources dynamically // List available resources dynamically
server.registerResourceList(async () => { server.registerResourceList(async () => {
const documents = await getAvailableDocuments(); const documents = await getAvailableDocuments();
return { return {
resources: documents.map(doc => ({ resources: documents.map((doc) => ({
uri: `file://documents/${doc.name}`, uri: `file://documents/${doc.name}`,
name: doc.name, name: doc.name,
mimeType: "text/plain", mimeType: 'text/plain',
description: doc.description description: doc.description,
})) })),
}; };
}); });
``` ```
**When to use Resources vs Tools:** **When to use Resources vs Tools:**
- **Resources**: For data access with simple URI-based parameters - **Resources**: For data access with simple URI-based parameters
- **Tools**: For complex operations requiring validation and business logic - **Tools**: For complex operations requiring validation and business logic
- **Resources**: When data is relatively static or template-based - **Resources**: When data is relatively static or template-based
@@ -821,8 +823,8 @@ The TypeScript SDK supports two main transport mechanisms:
#### Streamable HTTP (Recommended for Remote Servers) #### Streamable HTTP (Recommended for Remote Servers)
```typescript ```typescript
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from "express"; import express from 'express';
const app = express(); const app = express();
app.use(express.json()); app.use(express.json());
@@ -831,7 +833,7 @@ app.post('/mcp', async (req, res) => {
// Create new transport for each request (stateless, prevents request ID collisions) // Create new transport for each request (stateless, prevents request ID collisions)
const transport = new StreamableHTTPServerTransport({ const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, sessionIdGenerator: undefined,
enableJsonResponse: true enableJsonResponse: true,
}); });
res.on('close', () => transport.close()); res.on('close', () => transport.close());
@@ -846,13 +848,14 @@ app.listen(3000);
#### stdio (For Local Integrations) #### stdio (For Local Integrations)
```typescript ```typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const transport = new StdioServerTransport(); const transport = new StdioServerTransport();
await server.connect(transport); await server.connect(transport);
``` ```
**Transport selection:** **Transport selection:**
- **Streamable HTTP**: Web services, remote access, multiple clients - **Streamable HTTP**: Web services, remote access, multiple clients
- **stdio**: Command-line tools, local development, subprocess integration - **stdio**: Command-line tools, local development, subprocess integration
@@ -863,12 +866,12 @@ Notify clients when server state changes:
```typescript ```typescript
// Notify when tools list changes // Notify when tools list changes
server.notification({ server.notification({
method: "notifications/tools/list_changed" method: 'notifications/tools/list_changed',
}); });
// Notify when resources change // Notify when resources change
server.notification({ server.notification({
method: "notifications/resources/list_changed" method: 'notifications/resources/list_changed',
}); });
``` ```
@@ -917,6 +920,7 @@ Always ensure `npm run build` completes successfully before considering the impl
Before finalizing your Node/TypeScript MCP server implementation, ensure: Before finalizing your Node/TypeScript MCP server implementation, ensure:
### Strategic Design ### Strategic Design
- [ ] Tools enable complete workflows, not just API endpoint wrappers - [ ] Tools enable complete workflows, not just API endpoint wrappers
- [ ] Tool names reflect natural task subdivisions - [ ] Tool names reflect natural task subdivisions
- [ ] Response formats optimize for agent context efficiency - [ ] Response formats optimize for agent context efficiency
@@ -924,6 +928,7 @@ Before finalizing your Node/TypeScript MCP server implementation, ensure:
- [ ] Error messages guide agents toward correct usage - [ ] Error messages guide agents toward correct usage
### Implementation Quality ### Implementation Quality
- [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented - [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented
- [ ] All tools registered using `registerTool` with complete configuration - [ ] All tools registered using `registerTool` with complete configuration
- [ ] All tools include `title`, `description`, `inputSchema`, and `annotations` - [ ] All tools include `title`, `description`, `inputSchema`, and `annotations`
@@ -935,6 +940,7 @@ Before finalizing your Node/TypeScript MCP server implementation, ensure:
- [ ] Error messages are clear, actionable, and educational - [ ] Error messages are clear, actionable, and educational
### TypeScript Quality ### TypeScript Quality
- [ ] TypeScript interfaces are defined for all data structures - [ ] TypeScript interfaces are defined for all data structures
- [ ] Strict TypeScript is enabled in tsconfig.json - [ ] Strict TypeScript is enabled in tsconfig.json
- [ ] No use of `any` type - use `unknown` or proper types instead - [ ] No use of `any` type - use `unknown` or proper types instead
@@ -942,12 +948,14 @@ Before finalizing your Node/TypeScript MCP server implementation, ensure:
- [ ] Error handling uses proper type guards (e.g., `axios.isAxiosError`, `z.ZodError`) - [ ] Error handling uses proper type guards (e.g., `axios.isAxiosError`, `z.ZodError`)
### Advanced Features (where applicable) ### Advanced Features (where applicable)
- [ ] Resources registered for appropriate data endpoints - [ ] Resources registered for appropriate data endpoints
- [ ] Appropriate transport configured (stdio or streamable HTTP) - [ ] Appropriate transport configured (stdio or streamable HTTP)
- [ ] Notifications implemented for dynamic server capabilities - [ ] Notifications implemented for dynamic server capabilities
- [ ] Type-safe with SDK interfaces - [ ] Type-safe with SDK interfaces
### Project Configuration ### Project Configuration
- [ ] Package.json includes all necessary dependencies - [ ] Package.json includes all necessary dependencies
- [ ] Build script produces working JavaScript in dist/ directory - [ ] Build script produces working JavaScript in dist/ directory
- [ ] Main entry point is properly configured as dist/index.js - [ ] Main entry point is properly configured as dist/index.js
@@ -955,6 +963,7 @@ Before finalizing your Node/TypeScript MCP server implementation, ensure:
- [ ] tsconfig.json properly configured with strict mode - [ ] tsconfig.json properly configured with strict mode
### Code Quality ### Code Quality
- [ ] Pagination is properly implemented where applicable - [ ] Pagination is properly implemented where applicable
- [ ] Large responses check CHARACTER_LIMIT constant and truncate with clear messages - [ ] Large responses check CHARACTER_LIMIT constant and truncate with clear messages
- [ ] Filtering options are provided for potentially large result sets - [ ] Filtering options are provided for potentially large result sets
@@ -963,8 +972,9 @@ Before finalizing your Node/TypeScript MCP server implementation, ensure:
- [ ] Return types are consistent across similar operations - [ ] Return types are consistent across similar operations
### Testing and Build ### Testing and Build
- [ ] `npm run build` completes successfully without errors - [ ] `npm run build` completes successfully without errors
- [ ] dist/index.js created and executable - [ ] dist/index.js created and executable
- [ ] Server runs: `node dist/index.js --help` - [ ] Server runs: `node dist/index.js --help`
- [ ] All imports resolve correctly - [ ] All imports resolve correctly
- [ ] Sample tool calls work as expected - [ ] Sample tool calls work as expected
@@ -9,6 +9,7 @@ This document provides Python-specific best practices and examples for implement
## Quick Reference ## Quick Reference
### Key Imports ### Key Imports
```python ```python
from mcp.server.fastmcp import FastMCP from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field, field_validator, ConfigDict from pydantic import BaseModel, Field, field_validator, ConfigDict
@@ -18,11 +19,13 @@ import httpx
``` ```
### Server Initialization ### Server Initialization
```python ```python
mcp = FastMCP("service_mcp") mcp = FastMCP("service_mcp")
``` ```
### Tool Registration Pattern ### Tool Registration Pattern
```python ```python
@mcp.tool(name="tool_name", annotations={...}) @mcp.tool(name="tool_name", annotations={...})
async def tool_function(params: InputModel) -> str: async def tool_function(params: InputModel) -> str:
@@ -35,6 +38,7 @@ async def tool_function(params: InputModel) -> str:
## MCP Python SDK and FastMCP ## MCP Python SDK and FastMCP
The official MCP Python SDK provides FastMCP, a high-level framework for building MCP servers. It provides: The official MCP Python SDK provides FastMCP, a high-level framework for building MCP servers. It provides:
- Automatic description and inputSchema generation from function signatures and docstrings - Automatic description and inputSchema generation from function signatures and docstrings
- Pydantic model integration for input validation - Pydantic model integration for input validation
- Decorator-based tool registration with `@mcp.tool` - Decorator-based tool registration with `@mcp.tool`
@@ -45,10 +49,12 @@ The official MCP Python SDK provides FastMCP, a high-level framework for buildin
## Server Naming Convention ## Server Naming Convention
Python MCP servers must follow this naming pattern: Python MCP servers must follow this naming pattern:
- **Format**: `{service}_mcp` (lowercase with underscores) - **Format**: `{service}_mcp` (lowercase with underscores)
- **Examples**: `github_mcp`, `jira_mcp`, `stripe_mcp` - **Examples**: `github_mcp`, `jira_mcp`, `stripe_mcp`
The name should be: The name should be:
- General (not tied to specific features) - General (not tied to specific features)
- Descriptive of the service/API being integrated - Descriptive of the service/API being integrated
- Easy to infer from the task description - Easy to infer from the task description
@@ -61,6 +67,7 @@ The name should be:
Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names. Use snake_case for tool names (e.g., "search_users", "create_project", "get_channel_info") with clear, action-oriented names.
**Avoid Naming Conflicts**: Include the service context to prevent overlaps: **Avoid Naming Conflicts**: Include the service context to prevent overlaps:
- Use "slack_send_message" instead of just "send_message" - Use "slack_send_message" instead of just "send_message"
- Use "github_create_issue" instead of just "create_issue" - Use "github_create_issue" instead of just "create_issue"
- Use "asana_list_tasks" instead of just "list_tasks" - Use "asana_list_tasks" instead of just "list_tasks"
@@ -168,6 +175,7 @@ class UserSearchInput(BaseModel):
``` ```
**Markdown format**: **Markdown format**:
- Use headers, lists, and formatting for clarity - Use headers, lists, and formatting for clarity
- Convert timestamps to human-readable format (e.g., "2024-01-15 10:30:00 UTC" instead of epoch) - Convert timestamps to human-readable format (e.g., "2024-01-15 10:30:00 UTC" instead of epoch)
- Show display names with IDs in parentheses (e.g., "@john.doe (U123456)") - Show display names with IDs in parentheses (e.g., "@john.doe (U123456)")
@@ -175,6 +183,7 @@ class UserSearchInput(BaseModel):
- Group related information logically - Group related information logically
**JSON format**: **JSON format**:
- Return complete, structured data suitable for programmatic processing - Return complete, structured data suitable for programmatic processing
- Include all available fields and metadata - Include all available fields and metadata
- Use consistent field names and types - Use consistent field names and types
@@ -518,6 +527,7 @@ async def interactive_tool(resource_id: str, ctx: Context) -> str:
``` ```
**Context capabilities:** **Context capabilities:**
- `ctx.report_progress(progress, message)` - Report progress for long operations - `ctx.report_progress(progress, message)` - Report progress for long operations
- `ctx.log_info(message, data)` / `ctx.log_error()` / `ctx.log_debug()` - Logging - `ctx.log_info(message, data)` / `ctx.log_error()` / `ctx.log_debug()` - Logging
- `ctx.elicit(prompt, input_type)` - Request input from users - `ctx.elicit(prompt, input_type)` - Request input from users
@@ -548,6 +558,7 @@ async def get_setting(key: str, ctx: Context) -> str:
``` ```
**When to use Resources vs Tools:** **When to use Resources vs Tools:**
- **Resources**: For data access with simple parameters (URI templates) - **Resources**: For data access with simple parameters (URI templates)
- **Tools**: For complex operations with validation and business logic - **Tools**: For complex operations with validation and business logic
@@ -631,6 +642,7 @@ if __name__ == "__main__":
``` ```
**Transport selection:** **Transport selection:**
- **stdio**: Command-line tools, local integrations, subprocess execution - **stdio**: Command-line tools, local integrations, subprocess execution
- **Streamable HTTP**: Web services, remote access, multiple clients - **Streamable HTTP**: Web services, remote access, multiple clients
@@ -670,6 +682,7 @@ Your implementation MUST prioritize composability and code reuse:
Before finalizing your Python MCP server implementation, ensure: Before finalizing your Python MCP server implementation, ensure:
### Strategic Design ### Strategic Design
- [ ] Tools enable complete workflows, not just API endpoint wrappers - [ ] Tools enable complete workflows, not just API endpoint wrappers
- [ ] Tool names reflect natural task subdivisions - [ ] Tool names reflect natural task subdivisions
- [ ] Response formats optimize for agent context efficiency - [ ] Response formats optimize for agent context efficiency
@@ -677,6 +690,7 @@ Before finalizing your Python MCP server implementation, ensure:
- [ ] Error messages guide agents toward correct usage - [ ] Error messages guide agents toward correct usage
### Implementation Quality ### Implementation Quality
- [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented - [ ] FOCUSED IMPLEMENTATION: Most important and valuable tools implemented
- [ ] All tools have descriptive names and documentation - [ ] All tools have descriptive names and documentation
- [ ] Return types are consistent across similar operations - [ ] Return types are consistent across similar operations
@@ -688,6 +702,7 @@ Before finalizing your Python MCP server implementation, ensure:
- [ ] Outputs are properly validated and formatted - [ ] Outputs are properly validated and formatted
### Tool Configuration ### Tool Configuration
- [ ] All tools implement 'name' and 'annotations' in the decorator - [ ] All tools implement 'name' and 'annotations' in the decorator
- [ ] Annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) - [ ] Annotations correctly set (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- [ ] All tools use Pydantic BaseModel for input validation with Field() definitions - [ ] All tools use Pydantic BaseModel for input validation with Field() definitions
@@ -697,6 +712,7 @@ Before finalizing your Python MCP server implementation, ensure:
- [ ] Pydantic models handle input validation (no manual validation needed) - [ ] Pydantic models handle input validation (no manual validation needed)
### Advanced Features (where applicable) ### Advanced Features (where applicable)
- [ ] Context injection used for logging, progress, or elicitation - [ ] Context injection used for logging, progress, or elicitation
- [ ] Resources registered for appropriate data endpoints - [ ] Resources registered for appropriate data endpoints
- [ ] Lifespan management implemented for persistent connections - [ ] Lifespan management implemented for persistent connections
@@ -704,6 +720,7 @@ Before finalizing your Python MCP server implementation, ensure:
- [ ] Appropriate transport configured (stdio or streamable HTTP) - [ ] Appropriate transport configured (stdio or streamable HTTP)
### Code Quality ### Code Quality
- [ ] File includes proper imports including Pydantic imports - [ ] File includes proper imports including Pydantic imports
- [ ] Pagination is properly implemented where applicable - [ ] Pagination is properly implemented where applicable
- [ ] Filtering options are provided for potentially large result sets - [ ] Filtering options are provided for potentially large result sets
@@ -713,7 +730,8 @@ Before finalizing your Python MCP server implementation, ensure:
- [ ] Constants are defined at module level in UPPER_CASE - [ ] Constants are defined at module level in UPPER_CASE
### Testing ### Testing
- [ ] Server runs successfully: `python your_server.py --help` - [ ] Server runs successfully: `python your_server.py --help`
- [ ] All imports resolve correctly - [ ] All imports resolve correctly
- [ ] Sample tool calls work as expected - [ ] Sample tool calls work as expected
- [ ] Error scenarios handled gracefully - [ ] Error scenarios handled gracefully
@@ -1,6 +1,6 @@
--- ---
name: mosaic-deploy name: mosaic-deploy
description: "Full end-to-end deploy flow for Mosaic Stack projects: push branch → open PR → wait for CI → merge → redeploy Portainer stack. Use when deploying a feature branch to production or staging, or when asked to ship a completed feature. Orchestrates mosaic-gitea, mosaic-woodpecker, and mosaic-portainer skills." description: 'Full end-to-end deploy flow for Mosaic Stack projects: push branch → open PR → wait for CI → merge → redeploy Portainer stack. Use when deploying a feature branch to production or staging, or when asked to ship a completed feature. Orchestrates mosaic-gitea, mosaic-woodpecker, and mosaic-portainer skills.'
--- ---
# mosaic-deploy # mosaic-deploy
@@ -29,6 +29,7 @@ git push -u origin <branch>
``` ```
If CI fails, check: If CI fails, check:
```bash ```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials woodpecker source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials woodpecker
~/.config/mosaic/tools/woodpecker/pipeline-status.sh -r <org>/<repo> ~/.config/mosaic/tools/woodpecker/pipeline-status.sh -r <org>/<repo>
@@ -42,6 +43,7 @@ cd ~/src/<repo>
``` ```
For branch-protected repos (force merge): For branch-protected repos (force merge):
```bash ```bash
GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2) GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2)
curl -X POST "https://git.mosaicstack.dev/api/v1/repos/<org>/<repo>/pulls/<pr#>/merge" \ curl -X POST "https://git.mosaicstack.dev/api/v1/repos/<org>/<repo>/pulls/<pr#>/merge" \
@@ -57,6 +59,7 @@ source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer
``` ```
Check deployment: Check deployment:
```bash ```bash
~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name> ~/.config/mosaic/tools/portainer/stack-status.sh -n <stack-name>
~/.config/mosaic/tools/portainer/stack-logs.sh -n <stack-name> -l 50 ~/.config/mosaic/tools/portainer/stack-logs.sh -n <stack-name> -l 50
@@ -64,12 +67,12 @@ Check deployment:
## Stack Name Map ## Stack Name Map
| Project | Stack Name | | Project | Stack Name |
|---|---| | ------------ | -------------- |
| mosaic-stack | `mosaic-stack` | | mosaic-stack | `mosaic-stack` |
| sage-phr | `sage-phr` | | sage-phr | `sage-phr` |
| openbrain | `openbrain` | | openbrain | `openbrain` |
| firefly | `firefly` | | firefly | `firefly` |
## Notes ## Notes
@@ -12,11 +12,13 @@ Git operations via Mosaic wrapper scripts. Platform-aware (Gitea or GitHub).
Scripts auto-detect platform from git remote. Run from inside the repo directory. Scripts auto-detect platform from git remote. Run from inside the repo directory.
For force-merge (branch protection bypass): For force-merge (branch protection bypass):
```bash ```bash
GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2) GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2)
``` ```
Or use the credentials loader: Or use the credentials loader:
```bash ```bash
source ~/.config/mosaic/tools/_lib/credentials.sh source ~/.config/mosaic/tools/_lib/credentials.sh
load_credentials gitea-mosaicstack load_credentials gitea-mosaicstack
@@ -29,60 +31,63 @@ All scripts in `~/.config/mosaic/tools/git/`.
### Pull Requests ### Pull Requests
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | ---------------- | ----------------------- | -------------------------------------------------------- |
| `pr-create.sh` | Create a PR | `-t "Title" [-b "Body"] [-B base] [-H head] [-i issue#]` | | `pr-create.sh` | Create a PR | `-t "Title" [-b "Body"] [-B base] [-H head] [-i issue#]` |
| `pr-list.sh` | List open PRs | `[-s state]` | | `pr-list.sh` | List open PRs | `[-s state]` |
| `pr-view.sh` | View PR details | `-n <pr#>` | | `pr-view.sh` | View PR details | `-n <pr#>` |
| `pr-merge.sh` | Squash-merge a PR | `-n <pr#> [-d]` (delete branch) | | `pr-merge.sh` | Squash-merge a PR | `-n <pr#> [-d]` (delete branch) |
| `pr-close.sh` | Close a PR | `-n <pr#>` | | `pr-close.sh` | Close a PR | `-n <pr#>` |
| `pr-diff.sh` | Show PR diff | `-n <pr#>` | | `pr-diff.sh` | Show PR diff | `-n <pr#>` |
| `pr-review.sh` | Submit a review | `-n <pr#> [-a approve\|request-changes]` | | `pr-review.sh` | Submit a review | `-n <pr#> [-a approve\|request-changes]` |
| `pr-ci-wait.sh` | Wait for CI on PR | `-n <pr#>` | | `pr-ci-wait.sh` | Wait for CI on PR | `-n <pr#>` |
| `pr-metadata.sh` | Get PR metadata as JSON | `-n <pr#>` | | `pr-metadata.sh` | Get PR metadata as JSON | `-n <pr#>` |
### Issues ### Issues
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | ------------------ | --------------- | --------------------------------------------------- |
| `issue-create.sh` | Create an issue | `-t "Title" [-b "Body"] [-l labels] [-m milestone]` | | `issue-create.sh` | Create an issue | `-t "Title" [-b "Body"] [-l labels] [-m milestone]` |
| `issue-list.sh` | List issues | `[-s state] [-l label]` | | `issue-list.sh` | List issues | `[-s state] [-l label]` |
| `issue-view.sh` | View issue | `-n <issue#>` | | `issue-view.sh` | View issue | `-n <issue#>` |
| `issue-close.sh` | Close issue | `-n <issue#>` | | `issue-close.sh` | Close issue | `-n <issue#>` |
| `issue-comment.sh` | Add comment | `-n <issue#> -c "Comment"` | | `issue-comment.sh` | Add comment | `-n <issue#> -c "Comment"` |
| `issue-assign.sh` | Assign issue | `-n <issue#> -u username` | | `issue-assign.sh` | Assign issue | `-n <issue#> -u username` |
| `issue-edit.sh` | Edit issue | `-n <issue#> [-t title] [-b body]` | | `issue-edit.sh` | Edit issue | `-n <issue#> [-t title] [-b body]` |
### Milestones ### Milestones
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | --------------------- | ---------------- | ---------------------------- |
| `milestone-create.sh` | Create milestone | `-t "Title" [-d "due date"]` | | `milestone-create.sh` | Create milestone | `-t "Title" [-d "due date"]` |
| `milestone-list.sh` | List milestones | — | | `milestone-list.sh` | List milestones | — |
| `milestone-close.sh` | Close milestone | `-n <number>` | | `milestone-close.sh` | Close milestone | `-n <number>` |
### CI / Queue ### CI / Queue
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | -------------------- | ---------------------- | --------------------------- |
| `ci-queue-wait.sh` | Wait for CI queue slot | `[-t timeout] [-B branch]` | | `ci-queue-wait.sh` | Wait for CI queue slot | `[-t timeout] [-B branch]` |
| `detect-platform.sh` | Detect git platform | outputs `gitea` or `github` | | `detect-platform.sh` | Detect git platform | outputs `gitea` or `github` |
## Common Workflows ## Common Workflows
**Create PR from current branch:** **Create PR from current branch:**
```bash ```bash
cd ~/src/<repo> cd ~/src/<repo>
~/.config/mosaic/tools/git/pr-create.sh -t "feat: my feature" -b "Description" -i <issue#> ~/.config/mosaic/tools/git/pr-create.sh -t "feat: my feature" -b "Description" -i <issue#>
``` ```
**Merge a PR (squash, with CI queue guard):** **Merge a PR (squash, with CI queue guard):**
```bash ```bash
cd ~/src/<repo> cd ~/src/<repo>
~/.config/mosaic/tools/git/pr-merge.sh -n <pr#> -d ~/.config/mosaic/tools/git/pr-merge.sh -n <pr#> -d
``` ```
**Force-merge bypassing branch protection:** **Force-merge bypassing branch protection:**
```bash ```bash
GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2) GITEA_TOKEN=$(cat ~/.config/mosaic/credentials/gitea.env | grep TOKEN | cut -d= -f2)
curl -X POST "https://git.mosaicstack.dev/api/v1/repos/<org>/<repo>/pulls/<PR>/merge" \ curl -X POST "https://git.mosaicstack.dev/api/v1/repos/<org>/<repo>/pulls/<PR>/merge" \
@@ -10,6 +10,7 @@ Mosaic mission and orchestrator session management.
## Mandatory Pre-Flight ## Mandatory Pre-Flight
Before initiating Orchestrator mode, always read: Before initiating Orchestrator mode, always read:
1. `~/.config/mosaic/guides/ORCHESTRATOR.md` 1. `~/.config/mosaic/guides/ORCHESTRATOR.md`
2. `~/.config/mosaic/guides/E2E-DELIVERY.md` 2. `~/.config/mosaic/guides/E2E-DELIVERY.md`
@@ -47,15 +48,15 @@ mosaic doctor
Located in `~/.config/mosaic/tools/orchestrator/`. Located in `~/.config/mosaic/tools/orchestrator/`.
| Script | Purpose | | Script | Purpose |
|---|---| | -------------------- | --------------------------------------------- |
| `mission-init.sh` | Initialize mission.json and TASKS.md scaffold | | `mission-init.sh` | Initialize mission.json and TASKS.md scaffold |
| `mission-status.sh` | Show current mission + task state | | `mission-status.sh` | Show current mission + task state |
| `session-run.sh` | Launch an orchestrator session | | `session-run.sh` | Launch an orchestrator session |
| `session-resume.sh` | Resume a paused orchestrator session | | `session-resume.sh` | Resume a paused orchestrator session |
| `session-status.sh` | Check session health / lock status | | `session-status.sh` | Check session health / lock status |
| `smoke-test.sh` | Post-deploy smoke test | | `smoke-test.sh` | Post-deploy smoke test |
| `continue-prompt.sh` | Generate a continue prompt for session | | `continue-prompt.sh` | Generate a continue prompt for session |
## Mission Init Pattern ## Mission Init Pattern
@@ -86,6 +87,7 @@ mosaic coord run --project .
## Completion Gates ## Completion Gates
A task is NOT done until all of: A task is NOT done until all of:
- [ ] Code review ✓ - [ ] Code review ✓
- [ ] Security review ✓ - [ ] Security review ✓
- [ ] Tests GREEN ✓ - [ ] Tests GREEN ✓
@@ -21,31 +21,34 @@ load_credentials portainer
All scripts live in `~/.config/mosaic/tools/portainer/`. All scripts live in `~/.config/mosaic/tools/portainer/`.
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | ------------------- | ---------------------------- | ------------------------------ |
| `stack-list.sh` | List all stacks | — | | `stack-list.sh` | List all stacks | — |
| `stack-status.sh` | Status of a stack | `-n <name>` | | `stack-status.sh` | Status of a stack | `-n <name>` |
| `stack-redeploy.sh` | Redeploy (file or git-based) | `-n <name> [-p]` (pull images) | | `stack-redeploy.sh` | Redeploy (file or git-based) | `-n <name> [-p]` (pull images) |
| `stack-start.sh` | Start a stopped stack | `-n <name>` | | `stack-start.sh` | Start a stopped stack | `-n <name>` |
| `stack-stop.sh` | Stop a running stack | `-n <name>` | | `stack-stop.sh` | Stop a running stack | `-n <name>` |
| `stack-logs.sh` | Tail stack logs | `-n <name> [-l lines]` | | `stack-logs.sh` | Tail stack logs | `-n <name> [-l lines]` |
| `endpoint-list.sh` | List Portainer endpoints | — | | `endpoint-list.sh` | List Portainer endpoints | — |
## Common Workflows ## Common Workflows
**Redeploy a stack with fresh images:** **Redeploy a stack with fresh images:**
```bash ```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer
~/.config/mosaic/tools/portainer/stack-redeploy.sh -n mosaic-stack -p ~/.config/mosaic/tools/portainer/stack-redeploy.sh -n mosaic-stack -p
``` ```
**Check all stack statuses:** **Check all stack statuses:**
```bash ```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer
~/.config/mosaic/tools/portainer/stack-list.sh ~/.config/mosaic/tools/portainer/stack-list.sh
``` ```
**Tail logs for a service:** **Tail logs for a service:**
```bash ```bash
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials portainer
~/.config/mosaic/tools/portainer/stack-logs.sh -n mosaic-stack -l 100 ~/.config/mosaic/tools/portainer/stack-logs.sh -n mosaic-stack -l 100
@@ -47,10 +47,10 @@ login from the repo's `origin` host **before** running a wrapper:
git remote get-url origin # inspect the host, then: git remote get-url origin # inspect the host, then:
``` ```
| origin host | login | | origin host | login |
| --------------------- | -------------------------------------------- | | --------------------- | ---------------------------------------- |
| `git.uscllc.com` | `export GITEA_LOGIN=usc` | | `git.uscllc.com` | `export GITEA_LOGIN=usc` |
| `git.mosaicstack.dev` | default `mosaicstack` (no export needed) | | `git.mosaicstack.dev` | default `mosaicstack` (no export needed) |
## 3. Other tool suites under `~/.config/mosaic/tools/` ## 3. Other tool suites under `~/.config/mosaic/tools/`
@@ -1,6 +1,6 @@
--- ---
name: mosaic-woodpecker name: mosaic-woodpecker
description: "Manage Woodpecker CI pipelines for Mosaic Stack projects. Use when checking pipeline status, triggering pipelines, waiting for CI to complete, or debugging build failures. Wraps scripts in ~/.config/mosaic/tools/woodpecker/. CI URL: https://ci.mosaicstack.dev." description: 'Manage Woodpecker CI pipelines for Mosaic Stack projects. Use when checking pipeline status, triggering pipelines, waiting for CI to complete, or debugging build failures. Wraps scripts in ~/.config/mosaic/tools/woodpecker/. CI URL: https://ci.mosaicstack.dev.'
--- ---
# mosaic-woodpecker # mosaic-woodpecker
@@ -16,6 +16,7 @@ load_credentials woodpecker
``` ```
For a specific instance (e.g. `usc`): For a specific instance (e.g. `usc`):
```bash ```bash
load_credentials woodpecker-usc load_credentials woodpecker-usc
``` ```
@@ -24,22 +25,23 @@ load_credentials woodpecker-usc
All scripts in `~/.config/mosaic/tools/woodpecker/`. All scripts in `~/.config/mosaic/tools/woodpecker/`.
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | --------------------- | --------------------- | ---------------------------------------------- |
| `pipeline-status.sh` | Check pipeline status | `[-r owner/repo] [-n number] [-f json\|table]` | | `pipeline-status.sh` | Check pipeline status | `[-r owner/repo] [-n number] [-f json\|table]` |
| `pipeline-list.sh` | List recent pipelines | `[-r owner/repo] [-l limit]` | | `pipeline-list.sh` | List recent pipelines | `[-r owner/repo] [-l limit]` |
| `pipeline-trigger.sh` | Trigger a pipeline | `[-r owner/repo] [-b branch]` | | `pipeline-trigger.sh` | Trigger a pipeline | `[-r owner/repo] [-b branch]` |
CI queue management is in `~/.config/mosaic/tools/git/`: CI queue management is in `~/.config/mosaic/tools/git/`:
| Script | Purpose | Key flags | | Script | Purpose | Key flags |
|---|---|---| | ------------------ | ---------------------- | ------------------------------------------------ |
| `ci-queue-wait.sh` | Wait for CI queue slot | `[-t timeout_sec] [-B branch] [--purpose merge]` | | `ci-queue-wait.sh` | Wait for CI queue slot | `[-t timeout_sec] [-B branch] [--purpose merge]` |
| `pr-ci-wait.sh` | Wait for PR CI to pass | `-n <pr#> [-t timeout]` | | `pr-ci-wait.sh` | Wait for PR CI to pass | `-n <pr#> [-t timeout]` |
## Common Workflows ## Common Workflows
**Check latest pipeline on current repo:** **Check latest pipeline on current repo:**
```bash ```bash
cd ~/src/<repo> cd ~/src/<repo>
source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials woodpecker source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials woodpecker
@@ -47,17 +49,20 @@ source ~/.config/mosaic/tools/_lib/credentials.sh && load_credentials woodpecker
``` ```
**Check specific pipeline number:** **Check specific pipeline number:**
```bash ```bash
~/.config/mosaic/tools/woodpecker/pipeline-status.sh -n 42 ~/.config/mosaic/tools/woodpecker/pipeline-status.sh -n 42
``` ```
**Wait for CI before merging:** **Wait for CI before merging:**
```bash ```bash
cd ~/src/<repo> cd ~/src/<repo>
~/.config/mosaic/tools/git/pr-ci-wait.sh -n <pr#> ~/.config/mosaic/tools/git/pr-ci-wait.sh -n <pr#>
``` ```
**Trigger a pipeline on a branch:** **Trigger a pipeline on a branch:**
```bash ```bash
~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh -r mosaic/mosaic-stack -b main ~/.config/mosaic/tools/woodpecker/pipeline-trigger.sh -r mosaic/mosaic-stack -b main
``` ```
@@ -67,9 +67,10 @@ Comprehensive best practices and architecture guide for NestJS applications, des
- 9.2 [Use Message and Event Patterns Correctly](#92-use-message-and-event-patterns-correctly) - 9.2 [Use Message and Event Patterns Correctly](#92-use-message-and-event-patterns-correctly)
- 9.3 [Use Message Queues for Background Jobs](#93-use-message-queues-for-background-jobs) - 9.3 [Use Message Queues for Background Jobs](#93-use-message-queues-for-background-jobs)
10. [DevOps & Deployment](#10-devops-deployment) — **LOW-MEDIUM** 10. [DevOps & Deployment](#10-devops-deployment) — **LOW-MEDIUM**
- 10.1 [Implement Graceful Shutdown](#101-implement-graceful-shutdown)
- 10.2 [Use ConfigModule for Environment Configuration](#102-use-configmodule-for-environment-configuration) - 10.1 [Implement Graceful Shutdown](#101-implement-graceful-shutdown)
- 10.3 [Use Structured Logging](#103-use-structured-logging) - 10.2 [Use ConfigModule for Environment Configuration](#102-use-configmodule-for-environment-configuration)
- 10.3 [Use Structured Logging](#103-use-structured-logging)
--- ---
@@ -596,9 +597,7 @@ Create custom repositories to encapsulate complex queries and database logic. Th
// Complex queries in services // Complex queries in services
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(@InjectRepository(User) private repo: Repository<User>) {}
@InjectRepository(User) private repo: Repository<User>,
) {}
async findActiveWithOrders(minOrders: number): Promise<User[]> { async findActiveWithOrders(minOrders: number): Promise<User[]> {
// Complex query logic mixed with business logic // Complex query logic mixed with business logic
@@ -623,9 +622,7 @@ export class UsersService {
// Custom repository with encapsulated queries // Custom repository with encapsulated queries
@Injectable() @Injectable()
export class UsersRepository { export class UsersRepository {
constructor( constructor(@InjectRepository(User) private repo: Repository<User>) {}
@InjectRepository(User) private repo: Repository<User>,
) {}
async findById(id: string): Promise<User | null> { async findById(id: string): Promise<User | null> {
return this.repo.findOne({ where: { id } }); return this.repo.findOne({ where: { id } });
@@ -825,12 +822,12 @@ export class OrdersService {
// Testing is painful - must mock unused methods // Testing is painful - must mock unused methods
const mockNotificationService = { const mockNotificationService = {
sendEmail: jest.fn(), sendEmail: jest.fn(),
sendSms: jest.fn(), // Never used, but required sendSms: jest.fn(), // Never used, but required
sendPush: jest.fn(), // Never used, but required sendPush: jest.fn(), // Never used, but required
sendSlack: jest.fn(), // Never used, but required sendSlack: jest.fn(), // Never used, but required
logNotification: jest.fn(), // Never used, but required logNotification: jest.fn(), // Never used, but required
getDeliveryStatus: jest.fn(), // Never used, but required getDeliveryStatus: jest.fn(), // Never used, but required
retryFailed: jest.fn(), // Never used, but required retryFailed: jest.fn(), // Never used, but required
scheduleNotification: jest.fn(), // Never used, but required scheduleNotification: jest.fn(), // Never used, but required
}; };
``` ```
@@ -1123,9 +1120,7 @@ export class OrdersService {
```typescript ```typescript
// Shared test suite that any implementation must pass // Shared test suite that any implementation must pass
function testPaymentGatewayContract( function testPaymentGatewayContract(createGateway: () => PaymentGateway) {
createGateway: () => PaymentGateway,
) {
describe('PaymentGateway contract', () => { describe('PaymentGateway contract', () => {
let gateway: PaymentGateway; let gateway: PaymentGateway;
@@ -1142,13 +1137,11 @@ function testPaymentGatewayContract(
}); });
it('throws InvalidCurrencyException for unsupported currency', async () => { it('throws InvalidCurrencyException for unsupported currency', async () => {
await expect(gateway.charge(1000, 'INVALID')) await expect(gateway.charge(1000, 'INVALID')).rejects.toThrow(InvalidCurrencyException);
.rejects.toThrow(InvalidCurrencyException);
}); });
it('throws TransactionNotFoundException for invalid refund', async () => { it('throws TransactionNotFoundException for invalid refund', async () => {
await expect(gateway.refund('nonexistent')) await expect(gateway.refund('nonexistent')).rejects.toThrow(TransactionNotFoundException);
.rejects.toThrow(TransactionNotFoundException);
}); });
}); });
} }
@@ -1359,7 +1352,9 @@ interface PaymentGateway {
@Injectable() @Injectable()
export class StripeService implements PaymentGateway { export class StripeService implements PaymentGateway {
charge(amount: number) { /* ... */ } charge(amount: number) {
/* ... */
}
} }
@Injectable() @Injectable()
@@ -1398,9 +1393,7 @@ export class MockPaymentService implements PaymentGateway {
providers: [ providers: [
{ {
provide: PAYMENT_GATEWAY, provide: PAYMENT_GATEWAY,
useClass: process.env.NODE_ENV === 'test' useClass: process.env.NODE_ENV === 'test' ? MockPaymentService : StripeService,
? MockPaymentService
: StripeService,
}, },
], ],
exports: [PAYMENT_GATEWAY], exports: [PAYMENT_GATEWAY],
@@ -1410,9 +1403,7 @@ export class PaymentModule {}
// Injection // Injection
@Injectable() @Injectable()
export class OrdersService { export class OrdersService {
constructor( constructor(@Inject(PAYMENT_GATEWAY) private payment: PaymentGateway) {}
@Inject(PAYMENT_GATEWAY) private payment: PaymentGateway,
) {}
async createOrder(dto: CreateOrderDto) { async createOrder(dto: CreateOrderDto) {
await this.payment.charge(dto.amount); await this.payment.charge(dto.amount);
@@ -1774,14 +1765,10 @@ export class AllExceptionsFilter implements ExceptionFilter {
const request = ctx.getRequest<Request>(); const request = ctx.getRequest<Request>();
const status = const status =
exception instanceof HttpException exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR;
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message = const message =
exception instanceof HttpException exception instanceof HttpException ? exception.message : 'Internal server error';
? exception.message
: 'Internal server error';
this.logger.error( this.logger.error(
`${request.method} ${request.url}`, `${request.method} ${request.url}`,
@@ -1798,10 +1785,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
} }
// Register globally in main.ts // Register globally in main.ts
app.useGlobalFilters( app.useGlobalFilters(new AllExceptionsFilter(app.get(Logger)), new DomainExceptionFilter());
new AllExceptionsFilter(app.get(Logger)),
new DomainExceptionFilter(),
);
// Or via module // Or via module
@Module({ @Module({
@@ -2387,9 +2371,9 @@ export class UsersController {
// DTOs without validation decorators // DTOs without validation decorators
export class CreateUserDto { export class CreateUserDto {
name: string; // No validation name: string; // No validation
email: string; // Could be "not-an-email" email: string; // Could be "not-an-email"
age: number; // Could be "abc" or -999 age: number; // Could be "abc" or -999
} }
``` ```
@@ -2402,9 +2386,9 @@ async function bootstrap() {
app.useGlobalPipes( app.useGlobalPipes(
new ValidationPipe({ new ValidationPipe({
whitelist: true, // Strip unknown properties whitelist: true, // Strip unknown properties
forbidNonWhitelisted: true, // Throw on unknown properties forbidNonWhitelisted: true, // Throw on unknown properties
transform: true, // Auto-transform to DTO types transform: true, // Auto-transform to DTO types
transformOptions: { transformOptions: {
enableImplicitConversion: true, enableImplicitConversion: true,
}, },
@@ -2915,9 +2899,7 @@ export class UsersService {
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],
useFactory: (config: ConfigService) => ({ useFactory: (config: ConfigService) => ({
stores: [ stores: [new KeyvRedis(config.get('REDIS_URL'))],
new KeyvRedis(config.get('REDIS_URL')),
],
ttl: 60 * 1000, // Default 60s ttl: 60 * 1000, // Default 60s
}), }),
}), }),
@@ -3091,9 +3073,7 @@ describe('UsersController (e2e)', () => {
describe('/users/:id (GET)', () => { describe('/users/:id (GET)', () => {
it('should return 404 for non-existent user', () => { it('should return 404 for non-existent user', () => {
return request(app.getHttpServer()) return request(app.getHttpServer()).get('/users/non-existent-id').expect(404);
.get('/users/non-existent-id')
.expect(404);
}); });
}); });
}); });
@@ -3121,9 +3101,7 @@ describe('Protected Routes (e2e)', () => {
}); });
it('should return 401 without token', () => { it('should return 401 without token', () => {
return request(app.getHttpServer()) return request(app.getHttpServer()).get('/users/me').expect(401);
.get('/users/me')
.expect(401);
}); });
it('should return user profile with valid token', () => { it('should return user profile with valid token', () => {
@@ -3254,9 +3232,7 @@ describe('WeatherService', () => {
}); });
it('should handle API timeout', async () => { it('should handle API timeout', async () => {
httpService.get.mockReturnValue( httpService.get.mockReturnValue(throwError(() => new Error('ETIMEDOUT')));
throwError(() => new Error('ETIMEDOUT')),
);
await expect(service.getWeather('NYC')).rejects.toThrow('Weather service unavailable'); await expect(service.getWeather('NYC')).rejects.toThrow('Weather service unavailable');
}); });
@@ -3287,10 +3263,7 @@ describe('UsersService', () => {
}; };
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
providers: [ providers: [UsersService, { provide: getRepositoryToken(User), useValue: mockRepo }],
UsersService,
{ provide: getRepositoryToken(User), useValue: mockRepo },
],
}).compile(); }).compile();
service = module.get(UsersService); service = module.get(UsersService);
@@ -3433,9 +3406,9 @@ describe('UsersService', () => {
it('should throw on duplicate email', async () => { it('should throw on duplicate email', async () => {
repo.findOne.mockResolvedValue({ id: '1', email: 'test@test.com' }); repo.findOne.mockResolvedValue({ id: '1', email: 'test@test.com' });
await expect( await expect(service.create({ name: 'Test', email: 'test@test.com' })).rejects.toThrow(
service.create({ name: 'Test', email: 'test@test.com' }), ConflictException,
).rejects.toThrow(ConflictException); );
}); });
}); });
@@ -3813,12 +3786,7 @@ export class OrdersService {
for (const item of items) { for (const item of items) {
await manager.save(OrderItem, { orderId: order.id, ...item }); await manager.save(OrderItem, { orderId: order.id, ...item });
await manager.decrement( await manager.decrement(Inventory, { productId: item.productId }, 'stock', item.quantity);
Inventory,
{ productId: item.productId },
'stock',
item.quantity,
);
} }
// If this throws, everything rolls back // If this throws, everything rolls back
@@ -3841,12 +3809,7 @@ export class TransferService {
try { try {
// Debit source account // Debit source account
await queryRunner.manager.decrement( await queryRunner.manager.decrement(Account, { id: fromId }, 'balance', amount);
Account,
{ id: fromId },
'balance',
amount,
);
// Verify sufficient funds // Verify sufficient funds
const source = await queryRunner.manager.findOne(Account, { const source = await queryRunner.manager.findOne(Account, {
@@ -3857,12 +3820,7 @@ export class TransferService {
} }
// Credit destination account // Credit destination account
await queryRunner.manager.increment( await queryRunner.manager.increment(Account, { id: toId }, 'balance', amount);
Account,
{ id: toId },
'balance',
amount,
);
// Log the transaction // Log the transaction
await queryRunner.manager.save(TransactionLog, { await queryRunner.manager.save(TransactionLog, {
@@ -3890,10 +3848,7 @@ export class UsersRepository {
private dataSource: DataSource, private dataSource: DataSource,
) {} ) {}
async createWithProfile( async createWithProfile(userData: CreateUserDto, profileData: CreateProfileDto): Promise<User> {
userData: CreateUserDto,
profileData: CreateProfileDto,
): Promise<User> {
return this.dataSource.transaction(async (manager) => { return this.dataSource.transaction(async (manager) => {
const user = await manager.save(User, userData); const user = await manager.save(User, userData);
await manager.save(Profile, { ...profileData, userId: user.id }); await manager.save(Profile, { ...profileData, userId: user.id });
@@ -4034,7 +3989,7 @@ export class UsersController {
@SerializeOptions({ type: UserResponseDto }) @SerializeOptions({ type: UserResponseDto })
async findAll(): Promise<UserResponseDto[]> { async findAll(): Promise<UserResponseDto[]> {
const users = await this.usersService.findAll(); const users = await this.usersService.findAll();
return users.map(u => plainToInstance(UserResponseDto, u)); return users.map((u) => plainToInstance(UserResponseDto, u));
} }
@Get(':id') @Get(':id')
@@ -5137,11 +5092,7 @@ import { BullModule } from '@nestjs/bullmq';
}, },
}, },
}), }),
BullModule.registerQueue( BullModule.registerQueue({ name: 'email' }, { name: 'reports' }, { name: 'notifications' }),
{ name: 'email' },
{ name: 'reports' },
{ name: 'notifications' },
),
], ],
}) })
export class QueueModule {} export class QueueModule {}
@@ -5149,9 +5100,7 @@ export class QueueModule {}
// Producer: Add jobs to queue // Producer: Add jobs to queue
@Injectable() @Injectable()
export class ReportsService { export class ReportsService {
constructor( constructor(@InjectQueue('reports') private reportsQueue: Queue) {}
@InjectQueue('reports') private reportsQueue: Queue,
) {}
async requestReport(dto: GenerateReportDto): Promise<{ jobId: string }> { async requestReport(dto: GenerateReportDto): Promise<{ jobId: string }> {
// Return immediately, process in background // Return immediately, process in background
@@ -5406,9 +5355,7 @@ export class DatabaseService implements OnApplicationShutdown {
console.log(`Database service shutting down on ${signal}`); console.log(`Database service shutting down on ${signal}`);
// Close all connections gracefully // Close all connections gracefully
await Promise.all( await Promise.all(this.connections.map((conn) => conn.close()));
this.connections.map((conn) => conn.close()),
);
console.log('All database connections closed'); console.log('All database connections closed');
} }
@@ -5477,9 +5424,7 @@ export class HealthController {
throw new ServiceUnavailableException('Shutting down'); throw new ServiceUnavailableException('Shutting down');
} }
return this.health.check([ return this.health.check([() => this.db.pingCheck('database')]);
() => this.db.pingCheck('database'),
]);
} }
} }
@@ -5608,9 +5553,7 @@ export const appConfig = registerAs('app', () => ({
// config/validation.schema.ts // config/validation.schema.ts
export const validationSchema = Joi.object({ export const validationSchema = Joi.object({
NODE_ENV: Joi.string() NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000), PORT: Joi.number().default(3000),
DB_HOST: Joi.string().required(), DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().default(5432), DB_PORT: Joi.number().default(5432),
@@ -5906,10 +5849,7 @@ import { LoggerModule } from 'nestjs-pino';
LoggerModule.forRoot({ LoggerModule.forRoot({
pinoHttp: { pinoHttp: {
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport: transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' }
: undefined,
redact: ['req.headers.authorization', 'req.body.password'], redact: ['req.headers.authorization', 'req.body.password'],
serializers: { serializers: {
req: (req) => ({ req: (req) => ({
@@ -5955,4 +5895,4 @@ Reference: [NestJS Logger](https://docs.nestjs.com/techniques/logger)
--- ---
*Generated by build-agents.ts on 2026-01-16* _Generated by build-agents.ts on 2026-01-16_
@@ -4,7 +4,7 @@ description: NestJS best practices and architecture patterns for building produc
license: MIT license: MIT
metadata: metadata:
author: Kadajett author: Kadajett
version: "1.1.0" version: '1.1.0'
--- ---
# NestJS Best Practices # NestJS Best Practices
@@ -24,18 +24,18 @@ Reference these guidelines when:
## Rule Categories by Priority ## Rule Categories by Priority
| Priority | Category | Impact | Prefix | | Priority | Category | Impact | Prefix |
|----------|----------|--------|--------| | -------- | -------------------- | ----------- | ----------- |
| 1 | Architecture | CRITICAL | `arch-` | | 1 | Architecture | CRITICAL | `arch-` |
| 2 | Dependency Injection | CRITICAL | `di-` | | 2 | Dependency Injection | CRITICAL | `di-` |
| 3 | Error Handling | HIGH | `error-` | | 3 | Error Handling | HIGH | `error-` |
| 4 | Security | HIGH | `security-` | | 4 | Security | HIGH | `security-` |
| 5 | Performance | HIGH | `perf-` | | 5 | Performance | HIGH | `perf-` |
| 6 | Testing | MEDIUM-HIGH | `test-` | | 6 | Testing | MEDIUM-HIGH | `test-` |
| 7 | Database & ORM | MEDIUM-HIGH | `db-` | | 7 | Database & ORM | MEDIUM-HIGH | `db-` |
| 8 | API Design | MEDIUM | `api-` | | 8 | API Design | MEDIUM | `api-` |
| 9 | Microservices | MEDIUM | `micro-` | | 9 | Microservices | MEDIUM | `micro-` |
| 10 | DevOps & Deployment | LOW-MEDIUM | `devops-` | | 10 | DevOps & Deployment | LOW-MEDIUM | `devops-` |
## Quick Reference ## Quick Reference
@@ -120,6 +120,7 @@ rules/_sections.md
``` ```
Each rule file contains: Each rule file contains:
- Brief explanation of why it matters - Brief explanation of why it matters
- Incorrect code example with explanation - Incorrect code example with explanation
- Correct code example with explanation - Correct code example with explanation
@@ -126,7 +126,7 @@ export class UsersController {
@SerializeOptions({ type: UserResponseDto }) @SerializeOptions({ type: UserResponseDto })
async findAll(): Promise<UserResponseDto[]> { async findAll(): Promise<UserResponseDto[]> {
const users = await this.usersService.findAll(); const users = await this.usersService.findAll();
return users.map(u => plainToInstance(UserResponseDto, u)); return users.map((u) => plainToInstance(UserResponseDto, u));
} }
@Get(':id') @Get(':id')
@@ -1,7 +1,7 @@
--- ---
title: Avoid Circular Dependencies title: Avoid Circular Dependencies
impact: CRITICAL impact: CRITICAL
impactDescription: "#1 cause of runtime crashes" impactDescription: '#1 cause of runtime crashes'
tags: architecture, modules, dependencies tags: architecture, modules, dependencies
--- ---
@@ -1,7 +1,7 @@
--- ---
title: Organize by Feature Modules title: Organize by Feature Modules
impact: CRITICAL impact: CRITICAL
impactDescription: "3-5x faster onboarding and development" impactDescription: '3-5x faster onboarding and development'
tags: architecture, modules, organization tags: architecture, modules, organization
--- ---
@@ -1,7 +1,7 @@
--- ---
title: Single Responsibility for Services title: Single Responsibility for Services
impact: CRITICAL impact: CRITICAL
impactDescription: "40%+ improvement in testability" impactDescription: '40%+ improvement in testability'
tags: architecture, services, single-responsibility tags: architecture, services, single-responsibility
--- ---
@@ -15,9 +15,7 @@ Create custom repositories to encapsulate complex queries and database logic. Th
// Complex queries in services // Complex queries in services
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor( constructor(@InjectRepository(User) private repo: Repository<User>) {}
@InjectRepository(User) private repo: Repository<User>,
) {}
async findActiveWithOrders(minOrders: number): Promise<User[]> { async findActiveWithOrders(minOrders: number): Promise<User[]> {
// Complex query logic mixed with business logic // Complex query logic mixed with business logic
@@ -42,9 +40,7 @@ export class UsersService {
// Custom repository with encapsulated queries // Custom repository with encapsulated queries
@Injectable() @Injectable()
export class UsersRepository { export class UsersRepository {
constructor( constructor(@InjectRepository(User) private repo: Repository<User>) {}
@InjectRepository(User) private repo: Repository<User>,
) {}
async findById(id: string): Promise<User | null> { async findById(id: string): Promise<User | null> {
return this.repo.findOne({ where: { id } }); return this.repo.findOne({ where: { id } });
@@ -47,12 +47,7 @@ export class OrdersService {
for (const item of items) { for (const item of items) {
await manager.save(OrderItem, { orderId: order.id, ...item }); await manager.save(OrderItem, { orderId: order.id, ...item });
await manager.decrement( await manager.decrement(Inventory, { productId: item.productId }, 'stock', item.quantity);
Inventory,
{ productId: item.productId },
'stock',
item.quantity,
);
} }
// If this throws, everything rolls back // If this throws, everything rolls back
@@ -75,12 +70,7 @@ export class TransferService {
try { try {
// Debit source account // Debit source account
await queryRunner.manager.decrement( await queryRunner.manager.decrement(Account, { id: fromId }, 'balance', amount);
Account,
{ id: fromId },
'balance',
amount,
);
// Verify sufficient funds // Verify sufficient funds
const source = await queryRunner.manager.findOne(Account, { const source = await queryRunner.manager.findOne(Account, {
@@ -91,12 +81,7 @@ export class TransferService {
} }
// Credit destination account // Credit destination account
await queryRunner.manager.increment( await queryRunner.manager.increment(Account, { id: toId }, 'balance', amount);
Account,
{ id: toId },
'balance',
amount,
);
// Log the transaction // Log the transaction
await queryRunner.manager.save(TransactionLog, { await queryRunner.manager.save(TransactionLog, {
@@ -124,10 +109,7 @@ export class UsersRepository {
private dataSource: DataSource, private dataSource: DataSource,
) {} ) {}
async createWithProfile( async createWithProfile(userData: CreateUserDto, profileData: CreateProfileDto): Promise<User> {
userData: CreateUserDto,
profileData: CreateProfileDto,
): Promise<User> {
return this.dataSource.transaction(async (manager) => { return this.dataSource.transaction(async (manager) => {
const user = await manager.save(User, userData); const user = await manager.save(User, userData);
await manager.save(Profile, { ...profileData, userId: user.id }); await manager.save(Profile, { ...profileData, userId: user.id });
@@ -79,9 +79,7 @@ export class DatabaseService implements OnApplicationShutdown {
console.log(`Database service shutting down on ${signal}`); console.log(`Database service shutting down on ${signal}`);
// Close all connections gracefully // Close all connections gracefully
await Promise.all( await Promise.all(this.connections.map((conn) => conn.close()));
this.connections.map((conn) => conn.close()),
);
console.log('All database connections closed'); console.log('All database connections closed');
} }
@@ -150,9 +148,7 @@ export class HealthController {
throw new ServiceUnavailableException('Shutting down'); throw new ServiceUnavailableException('Shutting down');
} }
return this.health.check([ return this.health.check([() => this.db.pingCheck('database')]);
() => this.db.pingCheck('database'),
]);
} }
} }
@@ -61,9 +61,7 @@ export const appConfig = registerAs('app', () => ({
// config/validation.schema.ts // config/validation.schema.ts
export const validationSchema = Joi.object({ export const validationSchema = Joi.object({
NODE_ENV: Joi.string() NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000), PORT: Joi.number().default(3000),
DB_HOST: Joi.string().required(), DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().default(5432), DB_PORT: Joi.number().default(5432),
@@ -194,10 +194,7 @@ import { LoggerModule } from 'nestjs-pino';
LoggerModule.forRoot({ LoggerModule.forRoot({
pinoHttp: { pinoHttp: {
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport: transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined,
process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty' }
: undefined,
redact: ['req.headers.authorization', 'req.body.password'], redact: ['req.headers.authorization', 'req.body.password'],
serializers: { serializers: {
req: (req) => ({ req: (req) => ({
@@ -43,12 +43,12 @@ export class OrdersService {
// Testing is painful - must mock unused methods // Testing is painful - must mock unused methods
const mockNotificationService = { const mockNotificationService = {
sendEmail: jest.fn(), sendEmail: jest.fn(),
sendSms: jest.fn(), // Never used, but required sendSms: jest.fn(), // Never used, but required
sendPush: jest.fn(), // Never used, but required sendPush: jest.fn(), // Never used, but required
sendSlack: jest.fn(), // Never used, but required sendSlack: jest.fn(), // Never used, but required
logNotification: jest.fn(), // Never used, but required logNotification: jest.fn(), // Never used, but required
getDeliveryStatus: jest.fn(), // Never used, but required getDeliveryStatus: jest.fn(), // Never used, but required
retryFailed: jest.fn(), // Never used, but required retryFailed: jest.fn(), // Never used, but required
scheduleNotification: jest.fn(), // Never used, but required scheduleNotification: jest.fn(), // Never used, but required
}; };
``` ```
@@ -178,9 +178,7 @@ export class OrdersService {
```typescript ```typescript
// Shared test suite that any implementation must pass // Shared test suite that any implementation must pass
function testPaymentGatewayContract( function testPaymentGatewayContract(createGateway: () => PaymentGateway) {
createGateway: () => PaymentGateway,
) {
describe('PaymentGateway contract', () => { describe('PaymentGateway contract', () => {
let gateway: PaymentGateway; let gateway: PaymentGateway;
@@ -197,13 +195,11 @@ function testPaymentGatewayContract(
}); });
it('throws InvalidCurrencyException for unsupported currency', async () => { it('throws InvalidCurrencyException for unsupported currency', async () => {
await expect(gateway.charge(1000, 'INVALID')) await expect(gateway.charge(1000, 'INVALID')).rejects.toThrow(InvalidCurrencyException);
.rejects.toThrow(InvalidCurrencyException);
}); });
it('throws TransactionNotFoundException for invalid refund', async () => { it('throws TransactionNotFoundException for invalid refund', async () => {
await expect(gateway.refund('nonexistent')) await expect(gateway.refund('nonexistent')).rejects.toThrow(TransactionNotFoundException);
.rejects.toThrow(TransactionNotFoundException);
}); });
}); });
} }

Some files were not shown because too many files have changed in this diff Show More