opencastle 0.32.12 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -93
- package/README.md +5 -3
- package/package.json +2 -2
- package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +4 -4
- package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +12 -12
- package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/dist/data/convoys/demo-docs-update.json +7 -7
- package/src/dashboard/dist/data/convoys/demo-perf-opt.json +4 -4
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/public/data/convoys/demo-auth-revamp.json +4 -4
- package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +12 -12
- package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/public/data/convoys/demo-docs-update.json +7 -7
- package/src/dashboard/public/data/convoys/demo-perf-opt.json +4 -4
- package/src/orchestrator/customizations/stack/sanity-config.md +43 -0
- package/src/orchestrator/customizations/stack/supabase-config.md +53 -0
- package/src/orchestrator/plugins/astro/REFERENCE.md +5 -0
- package/src/orchestrator/plugins/astro/SKILL.md +22 -29
- package/src/orchestrator/plugins/chrome-devtools/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/chrome-devtools/SKILL.md +10 -55
- package/src/orchestrator/plugins/contentful/REFERENCE.md +16 -0
- package/src/orchestrator/plugins/contentful/SKILL.md +69 -29
- package/src/orchestrator/plugins/convex/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/convex/SKILL.md +13 -1
- package/src/orchestrator/plugins/cypress/REFERENCE.md +5 -0
- package/src/orchestrator/plugins/cypress/SKILL.md +29 -93
- package/src/orchestrator/plugins/figma/REFERENCE.md +18 -0
- package/src/orchestrator/plugins/figma/SKILL.md +41 -66
- package/src/orchestrator/plugins/jira/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/jira/SKILL.md +26 -114
- package/src/orchestrator/plugins/linear/SKILL.md +42 -109
- package/src/orchestrator/plugins/netlify/REFERENCE.md +33 -0
- package/src/orchestrator/plugins/netlify/SKILL.md +34 -64
- package/src/orchestrator/plugins/nextjs/REFERENCE.md +73 -0
- package/src/orchestrator/plugins/nextjs/SKILL.md +49 -138
- package/src/orchestrator/plugins/notion/SKILL.md +26 -168
- package/src/orchestrator/plugins/notion/TEMPLATES.md +88 -0
- package/src/orchestrator/plugins/nx/REFERENCE.md +10 -0
- package/src/orchestrator/plugins/nx/SKILL.md +12 -12
- package/src/orchestrator/plugins/playwright/REFERENCE.md +12 -0
- package/src/orchestrator/plugins/playwright/SKILL.md +33 -98
- package/src/orchestrator/plugins/prisma/REFERENCE.md +42 -0
- package/src/orchestrator/plugins/prisma/SKILL.md +18 -68
- package/src/orchestrator/plugins/resend/REFERENCE.md +61 -0
- package/src/orchestrator/plugins/resend/SKILL.md +23 -137
- package/src/orchestrator/plugins/sanity/SKILL.md +50 -3
- package/src/orchestrator/plugins/slack/REFERENCE.md +24 -0
- package/src/orchestrator/plugins/slack/SKILL.md +36 -111
- package/src/orchestrator/plugins/strapi/REFERENCE.md +35 -0
- package/src/orchestrator/plugins/strapi/SKILL.md +60 -24
- package/src/orchestrator/plugins/supabase/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/supabase/SKILL.md +44 -16
- package/src/orchestrator/plugins/teams/REFERENCE.md +36 -0
- package/src/orchestrator/plugins/teams/SKILL.md +35 -85
- package/src/orchestrator/plugins/trello/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/trello/SKILL.md +25 -97
- package/src/orchestrator/plugins/turborepo/REFERENCE.md +9 -0
- package/src/orchestrator/plugins/turborepo/SKILL.md +13 -1
- package/src/orchestrator/plugins/vercel/SKILL.md +45 -52
- package/src/orchestrator/plugins/vitest/SKILL.md +10 -14
- package/src/orchestrator/prompts/create-skill.prompt.md +62 -20
- package/src/orchestrator/prompts/generate-convoy.prompt.md +6 -0
- package/src/orchestrator/prompts/generate-prd.prompt.md +4 -0
- package/src/orchestrator/skills/accessibility-standards/REFERENCE.md +34 -0
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +6 -3
- package/src/orchestrator/skills/agent-hooks/HOOKS-REFERENCE.md +48 -0
- package/src/orchestrator/skills/agent-hooks/SKILL.md +41 -65
- package/src/orchestrator/skills/agent-memory/KNOWLEDGE-GRAPH.md +49 -0
- package/src/orchestrator/skills/agent-memory/SKILL.md +30 -67
- package/src/orchestrator/skills/api-patterns/SKILL.md +29 -1
- package/src/orchestrator/skills/backbone-scaffolding/EXAMPLES.md +16 -0
- package/src/orchestrator/skills/backbone-scaffolding/SKILL.md +99 -0
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/REFERENCE.md +70 -0
- package/src/orchestrator/skills/context-map/SKILL.md +28 -55
- package/src/orchestrator/skills/data-engineering/REFERENCE.md +55 -0
- package/src/orchestrator/skills/data-engineering/SKILL.md +40 -34
- package/src/orchestrator/skills/decomposition/REFERENCE.md +28 -0
- package/src/orchestrator/skills/decomposition/SKILL.md +15 -30
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +31 -65
- package/src/orchestrator/skills/documentation-standards/SKILL.md +31 -50
- package/src/orchestrator/skills/documentation-standards/WRITING-GUIDE.md +39 -0
- package/src/orchestrator/skills/fast-review/REFERENCE.md +30 -0
- package/src/orchestrator/skills/fast-review/SKILL.md +11 -31
- package/src/orchestrator/skills/frontend-design/COMPONENTS.md +113 -0
- package/src/orchestrator/skills/frontend-design/REFERENCE.md +36 -0
- package/src/orchestrator/skills/frontend-design/SKILL.md +36 -85
- package/src/orchestrator/skills/git-workflow/SKILL.md +13 -2
- package/src/orchestrator/skills/memory-merger/REFERENCE.md +20 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +29 -38
- package/src/orchestrator/skills/observability-logging/SKILL.md +5 -12
- package/src/orchestrator/skills/orchestration-protocols/REFERENCE.md +42 -0
- package/src/orchestrator/skills/orchestration-protocols/SKILL.md +54 -41
- package/src/orchestrator/skills/panel-majority-vote/REFERENCE.md +55 -0
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +30 -75
- package/src/orchestrator/skills/performance-optimization/SKILL.md +41 -1
- package/src/orchestrator/skills/project-consistency/SKILL.md +50 -89
- package/src/orchestrator/skills/project-consistency/TEMPLATES.md +39 -0
- package/src/orchestrator/skills/react-development/REFERENCE.md +7 -0
- package/src/orchestrator/skills/react-development/SKILL.md +50 -42
- package/src/orchestrator/skills/security-hardening/SKILL.md +88 -1
- package/src/orchestrator/skills/self-improvement/LESSON-CATEGORIES.md +36 -0
- package/src/orchestrator/skills/self-improvement/SKILL.md +19 -25
- package/src/orchestrator/skills/seo-patterns/REFERENCE.md +54 -0
- package/src/orchestrator/skills/seo-patterns/SKILL.md +20 -88
- package/src/orchestrator/skills/session-checkpoints/CHECKPOINT-TEMPLATE.md +58 -0
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +34 -58
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +37 -30
- package/src/orchestrator/skills/testing-workflow/SKILL.md +55 -2
- package/src/orchestrator/skills/validation-gates/REFERENCE.md +50 -0
- package/src/orchestrator/skills/validation-gates/SKILL.md +39 -35
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: playwright-testing
|
|
3
|
-
description: "Playwright E2E testing patterns, cross-browser configuration, page objects, and CI setup. Use when
|
|
3
|
+
description: "Playwright E2E testing patterns, cross-browser configuration, page objects, and CI setup. Use when creating E2E specs, visual regression suites, or configuring Playwright in CI. Trigger terms: playwright, e2e, trace, page object, cross-browser"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
|
|
7
7
|
|
|
8
8
|
# Playwright Testing
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
For project-specific test configuration and breakpoints, see [testing-config.md](../../.opencastle/stack/testing-config.md).
|
|
11
11
|
|
|
12
12
|
## Commands
|
|
13
13
|
|
|
@@ -24,85 +24,29 @@ npx playwright show-report # View HTML test report
|
|
|
24
24
|
npx playwright install # Install browsers
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## File Conventions
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
tests/
|
|
31
|
-
├── e2e/
|
|
32
|
-
│ ├── auth/
|
|
33
|
-
│ │ ├── login.spec.ts
|
|
34
|
-
│ │ └── signup.spec.ts
|
|
35
|
-
│ └── dashboard/
|
|
36
|
-
│ └── overview.spec.ts
|
|
37
|
-
├── fixtures/
|
|
38
|
-
│ └── auth.fixture.ts # Custom test fixtures
|
|
39
|
-
└── pages/
|
|
40
|
-
├── login.page.ts # Page object
|
|
41
|
-
└── dashboard.page.ts
|
|
42
|
-
```
|
|
29
|
+
Tests: `tests/e2e/{feature}/`, page objects: `tests/pages/`, fixtures: `tests/fixtures/`.
|
|
43
30
|
|
|
44
31
|
## Writing Tests
|
|
45
32
|
|
|
46
33
|
### Basic Test Pattern
|
|
47
34
|
|
|
48
|
-
```
|
|
35
|
+
```ts
|
|
49
36
|
import { test, expect } from '@playwright/test';
|
|
37
|
+
import { LoginPage } from '../pages/login-page';
|
|
50
38
|
|
|
51
39
|
test.describe('Login', () => {
|
|
52
|
-
test
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
await page.getByTestId('
|
|
58
|
-
await page.getByTestId('password-input').fill('password123');
|
|
59
|
-
await page.getByTestId('login-button').click();
|
|
60
|
-
|
|
61
|
-
await expect(page).toHaveURL(/.*dashboard/);
|
|
62
|
-
await expect(page.getByTestId('user-menu')).toBeVisible();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test('should show error for invalid credentials', async ({ page }) => {
|
|
66
|
-
await page.getByTestId('email-input').fill('wrong@example.com');
|
|
67
|
-
await page.getByTestId('password-input').fill('wrong');
|
|
68
|
-
await page.getByTestId('login-button').click();
|
|
69
|
-
|
|
70
|
-
await expect(page.getByTestId('error-message')).toContainText('Invalid credentials');
|
|
40
|
+
test('submits valid credentials', async ({ page }) => {
|
|
41
|
+
const login = new LoginPage(page);
|
|
42
|
+
await login.goto();
|
|
43
|
+
await login.fill('user@example.com', 'password123');
|
|
44
|
+
await page.getByRole('button', { name: 'Sign in' }).click();
|
|
45
|
+
await expect(page.getByTestId('dashboard')).toBeVisible();
|
|
71
46
|
});
|
|
72
47
|
});
|
|
73
48
|
```
|
|
74
49
|
|
|
75
|
-
### Page Object Model
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
// tests/pages/login.page.ts
|
|
79
|
-
import { type Page, type Locator } from '@playwright/test';
|
|
80
|
-
|
|
81
|
-
export class LoginPage {
|
|
82
|
-
readonly emailInput: Locator;
|
|
83
|
-
readonly passwordInput: Locator;
|
|
84
|
-
readonly submitButton: Locator;
|
|
85
|
-
readonly errorMessage: Locator;
|
|
86
|
-
|
|
87
|
-
constructor(private readonly page: Page) {
|
|
88
|
-
this.emailInput = page.getByTestId('email-input');
|
|
89
|
-
this.passwordInput = page.getByTestId('password-input');
|
|
90
|
-
this.submitButton = page.getByTestId('login-button');
|
|
91
|
-
this.errorMessage = page.getByTestId('error-message');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async goto() {
|
|
95
|
-
await this.page.goto('/login');
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async login(email: string, password: string) {
|
|
99
|
-
await this.emailInput.fill(email);
|
|
100
|
-
await this.passwordInput.fill(password);
|
|
101
|
-
await this.submitButton.click();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
50
|
## API Mocking
|
|
107
51
|
|
|
108
52
|
```typescript
|
|
@@ -114,13 +58,22 @@ await page.route('/api/login', (route) => {
|
|
|
114
58
|
|
|
115
59
|
## Locator Strategy
|
|
116
60
|
|
|
117
|
-
|
|
61
|
+
Prefer semantic locators; avoid brittle CSS selectors:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
page.getByRole('button', { name: 'Sign in' }) // preferred: ARIA role + accessible name
|
|
65
|
+
page.getByLabel('Email address') // form inputs by label
|
|
66
|
+
page.getByTestId('login-form') // test-id for complex containers
|
|
67
|
+
page.getByText('Welcome back') // visible text
|
|
68
|
+
page.locator('[data-state="open"]') // CSS only when no semantic alternative
|
|
69
|
+
```
|
|
118
70
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
71
|
+
## Quick Workflow: Write → Run → Debug → Verify
|
|
72
|
+
1. Write a focused spec using page objects and `test.describe`.
|
|
73
|
+
2. Run locally with `npx playwright test --ui` to iterate interactively.
|
|
74
|
+
3. On failure: collect a trace (`trace: 'on-first-retry'`), save an HTML report, and inspect via `npx playwright show-report`.
|
|
75
|
+
4. Validation checkpoint: confirm `npx playwright show-report` shows 0 failures and that saved traces contain the failing trace id.
|
|
76
|
+
5. Fix the test or app; re-run the spec and verify the HTML report is green.
|
|
124
77
|
|
|
125
78
|
## Configuration (playwright.config.ts)
|
|
126
79
|
|
|
@@ -153,27 +106,9 @@ export default defineConfig({
|
|
|
153
106
|
});
|
|
154
107
|
```
|
|
155
108
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
| `playwright/navigate` | Navigate to a URL |
|
|
163
|
-
| `playwright/screenshot` | Take page screenshots |
|
|
164
|
-
| `playwright/click` | Click elements |
|
|
165
|
-
| `playwright/fill` | Fill form inputs |
|
|
166
|
-
| `playwright/evaluate` | Execute JavaScript in browser |
|
|
167
|
-
| `playwright/expect` | Assert page state |
|
|
168
|
-
|
|
169
|
-
## Best Practices
|
|
170
|
-
|
|
171
|
-
- Use `test.describe` to group related tests
|
|
172
|
-
- Use `test.beforeEach` for common setup — keep tests independent
|
|
173
|
-
- Prefer `getByTestId` and `getByRole` over CSS selectors
|
|
174
|
-
- Use `expect(locator).toBeVisible()` before interacting
|
|
175
|
-
- Use `page.waitForURL()` or `page.waitForResponse()` instead of arbitrary waits
|
|
176
|
-
- Run tests in parallel (`fullyParallel: true`) for speed
|
|
177
|
-
- Use `trace: 'on-first-retry'` to debug flaky tests
|
|
178
|
-
- Use `codegen` to bootstrap tests, then refactor into page objects
|
|
179
|
-
- Use `page.route()` to mock API responses — create isolated, deterministic tests without backend dependencies
|
|
109
|
+
See REFERENCE.md for MCP tools and advanced config (moved to a referenced file).
|
|
110
|
+
## Best Practices (non-obvious)
|
|
111
|
+
|
|
112
|
+
- Use `trace: 'on-first-retry'` selectively for flaky suites; attach trace IDs to failure tickets
|
|
113
|
+
- Favor page-object reuse across specs but avoid global singletons that leak state between parallel workers
|
|
114
|
+
- For CI visual diffs, snapshot only stable regions and mask dynamic content before comparison
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
> Parent: [SKILL.md](./SKILL.md)
|
|
2
|
+
|
|
3
|
+
## Prisma Reference: Queries & Patterns
|
|
4
|
+
|
|
5
|
+
### Basic CRUD
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { PrismaClient } from '@prisma/client';
|
|
9
|
+
const prisma = new PrismaClient();
|
|
10
|
+
|
|
11
|
+
// Create
|
|
12
|
+
await prisma.user.create({ data: { email: 'user@example.com', name: 'Alice' } });
|
|
13
|
+
|
|
14
|
+
// Read (with relations)
|
|
15
|
+
await prisma.user.findUnique({ where: { id: userId }, include: { posts: true } });
|
|
16
|
+
|
|
17
|
+
// Update
|
|
18
|
+
await prisma.user.update({ where: { id: userId }, data: { name: 'Updated Name' } });
|
|
19
|
+
|
|
20
|
+
// Delete
|
|
21
|
+
await prisma.user.delete({ where: { id: userId } });
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Singleton client pattern
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { PrismaClient } from '@prisma/client';
|
|
28
|
+
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
|
|
29
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
|
30
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Best-practice reminders
|
|
34
|
+
- Use `select` for narrow reads; prefer transactions (`prisma.$transaction`) for multi-step updates.
|
|
35
|
+
- Catch `P2002` for unique constraint violations and handle gracefully.
|
|
36
|
+
Last Updated: 2026-03-31
|
|
37
|
+
|
|
38
|
+
Reference: Prisma migration & production checklist
|
|
39
|
+
|
|
40
|
+
- Inspection checklist for generated SQL and destructive changes
|
|
41
|
+
- CI pipeline snippet for `prisma migrate deploy` and `prisma generate`
|
|
42
|
+
- Handling `P2002` unique constraint errors and remediation patterns
|
|
@@ -7,7 +7,7 @@ description: "Prisma ORM schema design, migrations, client generation, and query
|
|
|
7
7
|
|
|
8
8
|
# Prisma Database
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
For project-specific database schema and connection details, see [database-config.md](../../.opencastle/stack/database-config.md).
|
|
11
11
|
|
|
12
12
|
## Commands
|
|
13
13
|
|
|
@@ -63,75 +63,25 @@ model Post {
|
|
|
63
63
|
}
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
### Schema Best Practices
|
|
67
|
-
|
|
68
|
-
- Use `cuid()` or `uuid()` for IDs — never auto-increment in distributed systems
|
|
69
|
-
- Always add `createdAt` and `updatedAt` timestamps
|
|
70
|
-
- Use `@map` and `@@map` to control database table/column names
|
|
71
|
-
- Add `@@index` for frequently queried columns
|
|
72
|
-
- Use `onDelete: Cascade` where appropriate
|
|
73
|
-
- Define relations explicitly with `@relation`
|
|
74
|
-
- Use enums for constrained string values
|
|
75
|
-
|
|
76
|
-
## Migration Rules
|
|
77
|
-
|
|
78
|
-
1. Always use `prisma migrate dev` in development — never `db push` for schema changes that need history
|
|
79
|
-
2. Name migrations descriptively: `npx prisma migrate dev --name add_reviews_table`
|
|
80
|
-
3. Review generated SQL before applying — Prisma auto-generates but may need manual adjustments
|
|
81
|
-
4. Test migrations locally before deploying
|
|
82
|
-
5. Use `prisma migrate deploy` in CI/CD — never `migrate dev` in production
|
|
83
|
-
6. Write seed scripts for development data in `prisma/seed.ts`
|
|
84
|
-
7. Never edit applied migration files — create new migrations instead
|
|
85
|
-
8. Run `prisma generate` after every schema change to update the client
|
|
86
|
-
|
|
87
|
-
## Query Patterns
|
|
88
|
-
|
|
89
|
-
### Basic CRUD
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
import { PrismaClient } from '@prisma/client';
|
|
93
|
-
const prisma = new PrismaClient();
|
|
94
|
-
|
|
95
|
-
// Create
|
|
96
|
-
const user = await prisma.user.create({
|
|
97
|
-
data: { email: 'user@example.com', name: 'Alice' },
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Read (with relations)
|
|
101
|
-
const userWithPosts = await prisma.user.findUnique({
|
|
102
|
-
where: { id: userId },
|
|
103
|
-
include: { posts: true },
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Update
|
|
107
|
-
const updated = await prisma.user.update({
|
|
108
|
-
where: { id: userId },
|
|
109
|
-
data: { name: 'Updated Name' },
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Delete
|
|
113
|
-
await prisma.user.delete({ where: { id: userId } });
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Singleton Pattern
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
// lib/prisma.ts
|
|
120
|
-
import { PrismaClient } from '@prisma/client';
|
|
66
|
+
### Schema Best Practices (Prisma gotchas)
|
|
121
67
|
|
|
122
|
-
|
|
68
|
+
- Use `cuid()` or `uuid()` for IDs in distributed systems (avoid serial increments).
|
|
69
|
+
- Always include `createdAt` and `updatedAt` timestamps for auditability and migrations.
|
|
70
|
+
- Prefer explicit `@relation` definitions and add `@@index` on frequently queried columns.
|
|
71
|
+
- Review `@map`/`@@map` uses when renaming to prevent accidental column/table mismatches.
|
|
72
|
+
- Avoid editing applied migration files; create corrective migrations instead.
|
|
123
73
|
|
|
124
|
-
|
|
74
|
+
## Migration Rules (Prisma-specific)
|
|
125
75
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
76
|
+
1. Use `npx prisma migrate dev` during development to generate migrations; inspect the generated SQL before applying.
|
|
77
|
+
2. Use `npx prisma migrate deploy` in CI/CD; never run `migrate dev` in production.
|
|
78
|
+
3. Name migrations descriptively and include backfill steps for destructive changes.
|
|
79
|
+
4. Regenerate the client after schema changes: `npx prisma generate`.
|
|
130
80
|
|
|
131
|
-
|
|
81
|
+
## Migration Workflow: validate → fix → retry (Prisma-focused)
|
|
82
|
+
1. Run `npx prisma migrate dev --name <desc>` locally to generate SQL.
|
|
83
|
+
2. Inspect `prisma/migrations/<timestamp>/migration.sql` for destructive operations and add backfills as needed.
|
|
84
|
+
3. Apply to a local/ephemeral DB and run tests; revert and adjust if failures occur.
|
|
85
|
+
4. Push migration to CI with `npx prisma migrate deploy` and assert clean application.
|
|
132
86
|
|
|
133
|
-
|
|
134
|
-
- Use `select` instead of `include` when you only need specific fields
|
|
135
|
-
- Use transactions (`prisma.$transaction`) for multi-step operations
|
|
136
|
-
- Paginate large result sets with `skip` and `take`
|
|
137
|
-
- Handle unique constraint violations with try/catch on `P2002` error code
|
|
87
|
+
For query patterns, singleton client pattern, and runnable CRUD examples see REFERENCE.md in this directory.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
> Parent: [SKILL.md](./SKILL.md)
|
|
2
|
+
|
|
3
|
+
## Resend Reference: Templates & Webhook Handler
|
|
4
|
+
|
|
5
|
+
### React Email template (compact example)
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
// emails/welcome.tsx
|
|
9
|
+
import { Html, Head, Body, Container, Heading, Text, Button } from '@react-email/components';
|
|
10
|
+
|
|
11
|
+
export function WelcomeEmail({ name, loginUrl = 'https://app.example.com/login' }: { name: string; loginUrl?: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<Html>
|
|
14
|
+
<Head />
|
|
15
|
+
<Body>
|
|
16
|
+
<Container>
|
|
17
|
+
<Heading>Welcome, {name}!</Heading>
|
|
18
|
+
<Text>Your account is ready.</Text>
|
|
19
|
+
<Button href={loginUrl}>Get started</Button>
|
|
20
|
+
</Container>
|
|
21
|
+
</Body>
|
|
22
|
+
</Html>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Webhook handler (verify signature)
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// app/api/webhooks/resend/route.ts
|
|
31
|
+
import { Webhook } from 'resend';
|
|
32
|
+
|
|
33
|
+
export async function POST(request: Request) {
|
|
34
|
+
const body = await request.text();
|
|
35
|
+
const signature = request.headers.get('svix-signature');
|
|
36
|
+
if (!signature || !process.env.RESEND_WEBHOOK_SECRET) return new Response('Missing signature', { status: 400 });
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET);
|
|
40
|
+
const event = webhook.verify(body, {
|
|
41
|
+
'svix-id': request.headers.get('svix-id')!,
|
|
42
|
+
'svix-timestamp': request.headers.get('svix-timestamp')!,
|
|
43
|
+
'svix-signature': signature,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// handle event.type (delivered, bounced, complained)
|
|
47
|
+
|
|
48
|
+
return new Response('OK', { status: 200 });
|
|
49
|
+
} catch (err) {
|
|
50
|
+
console.error('resend webhook verify failed', (err as Error).message);
|
|
51
|
+
return new Response('Invalid signature', { status: 400 });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
Last Updated: 2026-03-31
|
|
56
|
+
|
|
57
|
+
Reference: Resend verification & webhook troubleshooting
|
|
58
|
+
|
|
59
|
+
- DNS verification commands (dig examples) and expected DKIM/SPF headers
|
|
60
|
+
- Sample webhook test payloads and replay instructions
|
|
61
|
+
- Email header inspection checklist for deliverability debugging
|
|
@@ -3,185 +3,71 @@ name: resend-email
|
|
|
3
3
|
description: "Resend transactional email patterns, React Email templates, domain configuration, and webhook handling. Use when sending emails, building email templates, or configuring email delivery."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
|
|
7
|
-
|
|
8
6
|
# Resend Email
|
|
9
7
|
|
|
10
|
-
Resend-specific email sending patterns and React Email template conventions.
|
|
11
8
|
|
|
12
9
|
## Setup
|
|
13
10
|
|
|
14
11
|
```bash
|
|
15
12
|
npm install resend
|
|
16
|
-
npm install @react-email/components
|
|
13
|
+
npm install @react-email/components
|
|
17
14
|
```
|
|
18
15
|
|
|
19
|
-
### Client Initialization
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
// lib/resend.ts
|
|
23
|
-
import { Resend } from 'resend';
|
|
24
|
-
|
|
25
|
-
if (!process.env.RESEND_API_KEY) {
|
|
26
|
-
throw new Error('RESEND_API_KEY is required');
|
|
27
|
-
}
|
|
28
16
|
|
|
29
|
-
export const resend = new Resend(process.env.RESEND_API_KEY);
|
|
30
|
-
```
|
|
31
17
|
|
|
32
18
|
## Sending Emails
|
|
33
19
|
|
|
34
|
-
### Basic Send
|
|
35
|
-
|
|
36
20
|
```typescript
|
|
37
|
-
import {
|
|
21
|
+
import { Resend } from 'resend';
|
|
22
|
+
import { WelcomeEmail } from '@/emails/welcome';
|
|
38
23
|
|
|
24
|
+
const resend = new Resend(process.env.RESEND_API_KEY);
|
|
25
|
+
|
|
26
|
+
// Plain text
|
|
39
27
|
await resend.emails.send({
|
|
40
28
|
from: 'App <no-reply@yourdomain.com>',
|
|
41
29
|
to: ['user@example.com'],
|
|
42
|
-
subject: 'Welcome
|
|
43
|
-
html: '<p>
|
|
30
|
+
subject: 'Welcome',
|
|
31
|
+
html: '<p>Your account is ready.</p>',
|
|
44
32
|
});
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### With React Email Template
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { resend } from '@/lib/resend';
|
|
51
|
-
import { WelcomeEmail } from '@/emails/welcome';
|
|
52
33
|
|
|
34
|
+
// With React Email template
|
|
53
35
|
await resend.emails.send({
|
|
54
36
|
from: 'App <no-reply@yourdomain.com>',
|
|
55
37
|
to: ['user@example.com'],
|
|
56
|
-
subject: 'Welcome
|
|
38
|
+
subject: 'Welcome',
|
|
57
39
|
react: WelcomeEmail({ name: 'Alice' }),
|
|
58
40
|
});
|
|
59
41
|
```
|
|
60
42
|
|
|
61
43
|
## React Email Templates
|
|
62
44
|
|
|
63
|
-
### Template Structure
|
|
64
45
|
|
|
65
|
-
|
|
66
|
-
emails/
|
|
67
|
-
├── welcome.tsx
|
|
68
|
-
├── password-reset.tsx
|
|
69
|
-
├── invoice.tsx
|
|
70
|
-
└── components/
|
|
71
|
-
├── header.tsx
|
|
72
|
-
├── footer.tsx
|
|
73
|
-
└── button.tsx
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Template Pattern
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
// emails/welcome.tsx
|
|
80
|
-
import {
|
|
81
|
-
Html, Head, Body, Container, Section,
|
|
82
|
-
Heading, Text, Button, Img, Hr,
|
|
83
|
-
} from '@react-email/components';
|
|
84
|
-
|
|
85
|
-
interface WelcomeEmailProps {
|
|
86
|
-
name: string;
|
|
87
|
-
loginUrl?: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function WelcomeEmail({
|
|
91
|
-
name,
|
|
92
|
-
loginUrl = 'https://app.example.com/login',
|
|
93
|
-
}: WelcomeEmailProps) {
|
|
94
|
-
return (
|
|
95
|
-
<Html lang="en">
|
|
96
|
-
<Head />
|
|
97
|
-
<Body style={bodyStyle}>
|
|
98
|
-
<Container style={containerStyle}>
|
|
99
|
-
<Heading style={headingStyle}>Welcome, {name}!</Heading>
|
|
100
|
-
<Text style={textStyle}>
|
|
101
|
-
Your account has been created successfully.
|
|
102
|
-
</Text>
|
|
103
|
-
<Section style={{ textAlign: 'center' as const }}>
|
|
104
|
-
<Button href={loginUrl} style={buttonStyle}>
|
|
105
|
-
Get Started
|
|
106
|
-
</Button>
|
|
107
|
-
</Section>
|
|
108
|
-
<Hr style={hrStyle} />
|
|
109
|
-
<Text style={footerStyle}>
|
|
110
|
-
© 2026 Your App. All rights reserved.
|
|
111
|
-
</Text>
|
|
112
|
-
</Container>
|
|
113
|
-
</Body>
|
|
114
|
-
</Html>
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const bodyStyle = { backgroundColor: '#f6f9fc', fontFamily: 'sans-serif' };
|
|
119
|
-
const containerStyle = { margin: '0 auto', padding: '40px 20px', maxWidth: '580px' };
|
|
120
|
-
const headingStyle = { fontSize: '24px', color: '#1a1a1a' };
|
|
121
|
-
const textStyle = { fontSize: '16px', color: '#4a4a4a', lineHeight: '26px' };
|
|
122
|
-
const buttonStyle = {
|
|
123
|
-
backgroundColor: '#3b82f6', color: '#fff', fontSize: '16px',
|
|
124
|
-
padding: '12px 24px', borderRadius: '6px', textDecoration: 'none',
|
|
125
|
-
};
|
|
126
|
-
const hrStyle = { borderColor: '#e6e6e6', margin: '32px 0' };
|
|
127
|
-
const footerStyle = { fontSize: '12px', color: '#999' };
|
|
128
|
-
|
|
129
|
-
export default WelcomeEmail;
|
|
130
|
-
```
|
|
46
|
+
Template pattern and webhook handler examples: see [REFERENCE.md](./REFERENCE.md).
|
|
131
47
|
|
|
132
48
|
### Preview Templates
|
|
133
49
|
|
|
134
50
|
```bash
|
|
135
|
-
npx email dev
|
|
51
|
+
npx email dev
|
|
136
52
|
```
|
|
137
53
|
|
|
138
54
|
## Domain Configuration
|
|
139
55
|
|
|
140
56
|
1. Add your domain at resend.com → Domains
|
|
141
|
-
2. Configure DNS records (SPF, DKIM, DMARC)
|
|
142
|
-
3.
|
|
57
|
+
2. Configure DNS records (SPF, DKIM, DMARC) and await verification (<1h)
|
|
58
|
+
3. Post-verify: send one canned test to `postmaster@` and confirm headers (SPF/DKIM pass).
|
|
143
59
|
4. Use `from: 'Name <no-reply@yourdomain.com>'` in sends
|
|
144
60
|
|
|
145
61
|
## Webhook Handling
|
|
146
62
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const webhook = new Webhook(process.env.RESEND_WEBHOOK_SECRET!);
|
|
156
|
-
const event = webhook.verify(body, {
|
|
157
|
-
'svix-id': request.headers.get('svix-id')!,
|
|
158
|
-
'svix-timestamp': request.headers.get('svix-timestamp')!,
|
|
159
|
-
'svix-signature': signature!,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
switch (event.type) {
|
|
163
|
-
case 'email.delivered':
|
|
164
|
-
// Handle successful delivery
|
|
165
|
-
break;
|
|
166
|
-
case 'email.bounced':
|
|
167
|
-
// Handle bounce — remove from mailing list
|
|
168
|
-
break;
|
|
169
|
-
case 'email.complained':
|
|
170
|
-
// Handle spam complaint — unsubscribe immediately
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return new Response('OK', { status: 200 });
|
|
175
|
-
}
|
|
176
|
-
```
|
|
63
|
+
Webhook handling patterns and a safe, verified handler example are in REFERENCE.md (this directory).
|
|
64
|
+
|
|
65
|
+
## Quick Verification Workflow
|
|
66
|
+
1. Add domain and copy DNS records.
|
|
67
|
+
2. Verify DNS propagation: `dig TXT yourdomain.com` — confirm SPF/DKIM records appear.
|
|
68
|
+
3. Send a test email and confirm SPF/DKIM pass in headers.
|
|
69
|
+
4. Deploy webhook handler; test with: `curl -X POST -H 'Content-Type: application/json' -d '{"type":"email.delivered"}' https://yourapp.com/api/webhooks/resend` — assert 200.
|
|
70
|
+
5. If verification fails: re-check DNS, API keys, and webhook secret.
|
|
177
71
|
|
|
178
|
-
|
|
72
|
+
See REFERENCE.md for detailed verification commands and troubleshooting.
|
|
179
73
|
|
|
180
|
-
- Always use a verified custom domain — never send from `onboarding@resend.dev` in production
|
|
181
|
-
- Use React Email templates for complex emails — plain HTML for simple transactional messages
|
|
182
|
-
- Handle bounces and complaints via webhooks — remove invalid addresses promptly
|
|
183
|
-
- Use `RESEND_API_KEY` as an environment variable — never commit it
|
|
184
|
-
- Test emails locally with React Email dev server before deploying
|
|
185
|
-
- Set appropriate `from` addresses: `no-reply@` for transactional, named sender for marketing
|
|
186
|
-
- Include unsubscribe links where legally required (CAN-SPAM, GDPR)
|
|
187
|
-
- Keep email templates responsive — test in major email clients
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sanity-cms
|
|
3
|
-
description: "Sanity CMS
|
|
3
|
+
description: "Manages Sanity CMS schemas, GROQ queries, dataset exports/imports, and Studio configuration. Use when updating Sanity schemas, running GROQ or Vision queries, exporting datasets, modifying content models, or configuring a headless CMS with Sanity.io."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .opencastle/ directory instead. -->
|
|
@@ -13,8 +13,55 @@ Generic Sanity CMS development methodology. For project-specific configuration,
|
|
|
13
13
|
|
|
14
14
|
1. **Always check the schema before querying** — use `get_schema` to understand document types and field structures before writing GROQ queries
|
|
15
15
|
2. **Array vs single reference** — always verify whether a field is an array of references or a single reference; using the wrong query operator causes silent failures
|
|
16
|
-
3. **Local schema files are source of truth** —
|
|
16
|
+
3. **Local schema files are source of truth** — never edit deployed schemas directly; always change local files and redeploy
|
|
17
17
|
4. **Follow `defineType` and `defineField` patterns** — always use Sanity helpers for type safety and consistency
|
|
18
18
|
5. **Test GROQ queries in Vision** — validate queries against real data in the Vision plugin before deploying
|
|
19
|
-
6. **Handle draft/publish workflow** —
|
|
19
|
+
6. **Handle draft/publish workflow** — always account for the `drafts.` prefix when querying or mutating documents
|
|
20
20
|
7. **Keep queries in the shared library** — queries belong in a shared queries library, never inline in components
|
|
21
|
+
|
|
22
|
+
## Quick GROQ examples
|
|
23
|
+
|
|
24
|
+
Fetch published articles with authors populated:
|
|
25
|
+
|
|
26
|
+
```groq
|
|
27
|
+
*[_type == "article" && defined(publishedAt)] | order(publishedAt desc) {
|
|
28
|
+
_id,
|
|
29
|
+
title,
|
|
30
|
+
"author": author-> { name, _id },
|
|
31
|
+
body
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Filter by tag and return first 10:
|
|
36
|
+
|
|
37
|
+
```groq
|
|
38
|
+
*[_type == "article" && "tech" in tags[]] | order(_createdAt desc)[0..9] { title, slug }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Example schema (executable)
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
// schemas/article.js
|
|
45
|
+
import { defineType, defineField } from 'sanity'
|
|
46
|
+
|
|
47
|
+
export default defineType({
|
|
48
|
+
name: 'article',
|
|
49
|
+
title: 'Article',
|
|
50
|
+
type: 'document',
|
|
51
|
+
fields: [
|
|
52
|
+
defineField({ name: 'title', type: 'string', title: 'Title' }),
|
|
53
|
+
defineField({ name: 'slug', type: 'slug', options: { source: 'title' } }),
|
|
54
|
+
defineField({ name: 'author', type: 'reference', to: [{ type: 'author' }] }),
|
|
55
|
+
defineField({ name: 'body', type: 'array', of: [{ type: 'block' }] }),
|
|
56
|
+
],
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Workflow: change schema → validate → deploy
|
|
61
|
+
|
|
62
|
+
1. Update local schema files and run `sanity start` to surface schema errors locally.
|
|
63
|
+
2. Run Vision queries to validate GROQ results against local data: execute representative queries (see examples above).
|
|
64
|
+
3. Create a migration or dataset export if needed and test changes in a staging project/dataset.
|
|
65
|
+
- Validation checkpoint: run `sanity dataset export` and `sanity dataset import` into a temporary dataset and run queries.
|
|
66
|
+
4. Deploy the schema to Studio and run end-to-end site builds to confirm runtime behavior.
|
|
67
|
+
5. If validation fails at any step, revert schema changes locally, fix, and repeat from step 1.
|