opencastle 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +12 -69
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli/doctor.js +13 -7
  4. package/dist/cli/doctor.js.map +1 -1
  5. package/dist/cli/init.d.ts.map +1 -1
  6. package/dist/cli/init.js +2 -1
  7. package/dist/cli/init.js.map +1 -1
  8. package/dist/cli/init.test.d.ts +17 -0
  9. package/dist/cli/init.test.d.ts.map +1 -0
  10. package/dist/cli/init.test.js +881 -0
  11. package/dist/cli/init.test.js.map +1 -0
  12. package/dist/cli/mcp.d.ts +9 -0
  13. package/dist/cli/mcp.d.ts.map +1 -1
  14. package/dist/cli/mcp.js +56 -0
  15. package/dist/cli/mcp.js.map +1 -1
  16. package/dist/cli/run/adapters/copilot.d.ts +10 -2
  17. package/dist/cli/run/adapters/copilot.d.ts.map +1 -1
  18. package/dist/cli/run/adapters/copilot.js +83 -56
  19. package/dist/cli/run/adapters/copilot.js.map +1 -1
  20. package/dist/cli/run.js +2 -2
  21. package/dist/cli/run.js.map +1 -1
  22. package/dist/cli/stack-config-update.test.d.ts +2 -0
  23. package/dist/cli/stack-config-update.test.d.ts.map +1 -0
  24. package/dist/cli/stack-config-update.test.js +185 -0
  25. package/dist/cli/stack-config-update.test.js.map +1 -0
  26. package/dist/cli/stack-config.d.ts +27 -0
  27. package/dist/cli/stack-config.d.ts.map +1 -1
  28. package/dist/cli/stack-config.js +80 -27
  29. package/dist/cli/stack-config.js.map +1 -1
  30. package/dist/cli/types.d.ts +1 -1
  31. package/dist/cli/types.d.ts.map +1 -1
  32. package/dist/cli/update.d.ts.map +1 -1
  33. package/dist/cli/update.js +184 -17
  34. package/dist/cli/update.js.map +1 -1
  35. package/dist/orchestrator/plugins/astro/config.d.ts +3 -0
  36. package/dist/orchestrator/plugins/astro/config.d.ts.map +1 -0
  37. package/dist/orchestrator/plugins/astro/config.js +27 -0
  38. package/dist/orchestrator/plugins/astro/config.js.map +1 -0
  39. package/dist/orchestrator/plugins/chrome-devtools/config.js +2 -2
  40. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -1
  41. package/dist/orchestrator/plugins/contentful/config.js +1 -1
  42. package/dist/orchestrator/plugins/contentful/config.js.map +1 -1
  43. package/dist/orchestrator/plugins/convex/config.js +1 -1
  44. package/dist/orchestrator/plugins/convex/config.js.map +1 -1
  45. package/dist/orchestrator/plugins/cypress/config.d.ts +3 -0
  46. package/dist/orchestrator/plugins/cypress/config.d.ts.map +1 -0
  47. package/dist/orchestrator/plugins/cypress/config.js +15 -0
  48. package/dist/orchestrator/plugins/cypress/config.js.map +1 -0
  49. package/dist/orchestrator/plugins/figma/config.d.ts +3 -0
  50. package/dist/orchestrator/plugins/figma/config.d.ts.map +1 -0
  51. package/dist/orchestrator/plugins/figma/config.js +33 -0
  52. package/dist/orchestrator/plugins/figma/config.js.map +1 -0
  53. package/dist/orchestrator/plugins/index.d.ts.map +1 -1
  54. package/dist/orchestrator/plugins/index.js +20 -0
  55. package/dist/orchestrator/plugins/index.js.map +1 -1
  56. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -1
  57. package/dist/orchestrator/plugins/jira/config.js +2 -3
  58. package/dist/orchestrator/plugins/jira/config.js.map +1 -1
  59. package/dist/orchestrator/plugins/linear/config.js +2 -2
  60. package/dist/orchestrator/plugins/linear/config.js.map +1 -1
  61. package/dist/orchestrator/plugins/netlify/config.d.ts +3 -0
  62. package/dist/orchestrator/plugins/netlify/config.d.ts.map +1 -0
  63. package/dist/orchestrator/plugins/netlify/config.js +30 -0
  64. package/dist/orchestrator/plugins/netlify/config.js.map +1 -0
  65. package/dist/orchestrator/plugins/nextjs/config.d.ts +3 -0
  66. package/dist/orchestrator/plugins/nextjs/config.d.ts.map +1 -0
  67. package/dist/orchestrator/plugins/nextjs/config.js +35 -0
  68. package/dist/orchestrator/plugins/nextjs/config.js.map +1 -0
  69. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -1
  70. package/dist/orchestrator/plugins/nx/config.js +2 -3
  71. package/dist/orchestrator/plugins/nx/config.js.map +1 -1
  72. package/dist/orchestrator/plugins/playwright/config.d.ts +3 -0
  73. package/dist/orchestrator/plugins/playwright/config.d.ts.map +1 -0
  74. package/dist/orchestrator/plugins/playwright/config.js +25 -0
  75. package/dist/orchestrator/plugins/playwright/config.js.map +1 -0
  76. package/dist/orchestrator/plugins/prisma/config.d.ts +3 -0
  77. package/dist/orchestrator/plugins/prisma/config.d.ts.map +1 -0
  78. package/dist/orchestrator/plugins/prisma/config.js +25 -0
  79. package/dist/orchestrator/plugins/prisma/config.js.map +1 -0
  80. package/dist/orchestrator/plugins/resend/config.d.ts +3 -0
  81. package/dist/orchestrator/plugins/resend/config.d.ts.map +1 -0
  82. package/dist/orchestrator/plugins/resend/config.js +46 -0
  83. package/dist/orchestrator/plugins/resend/config.js.map +1 -0
  84. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -1
  85. package/dist/orchestrator/plugins/sanity/config.js +1 -2
  86. package/dist/orchestrator/plugins/sanity/config.js.map +1 -1
  87. package/dist/orchestrator/plugins/slack/config.js +1 -1
  88. package/dist/orchestrator/plugins/slack/config.js.map +1 -1
  89. package/dist/orchestrator/plugins/strapi/config.js +1 -1
  90. package/dist/orchestrator/plugins/strapi/config.js.map +1 -1
  91. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -1
  92. package/dist/orchestrator/plugins/supabase/config.js +1 -2
  93. package/dist/orchestrator/plugins/supabase/config.js.map +1 -1
  94. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -1
  95. package/dist/orchestrator/plugins/teams/config.js +1 -2
  96. package/dist/orchestrator/plugins/teams/config.js.map +1 -1
  97. package/dist/orchestrator/plugins/turborepo/config.d.ts +3 -0
  98. package/dist/orchestrator/plugins/turborepo/config.d.ts.map +1 -0
  99. package/dist/orchestrator/plugins/turborepo/config.js +15 -0
  100. package/dist/orchestrator/plugins/turborepo/config.js.map +1 -0
  101. package/dist/orchestrator/plugins/types.d.ts +7 -7
  102. package/dist/orchestrator/plugins/types.d.ts.map +1 -1
  103. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -1
  104. package/dist/orchestrator/plugins/vercel/config.js +2 -3
  105. package/dist/orchestrator/plugins/vercel/config.js.map +1 -1
  106. package/dist/orchestrator/plugins/vitest/config.d.ts +3 -0
  107. package/dist/orchestrator/plugins/vitest/config.d.ts.map +1 -0
  108. package/dist/orchestrator/plugins/vitest/config.js +15 -0
  109. package/dist/orchestrator/plugins/vitest/config.js.map +1 -0
  110. package/package.json +2 -1
  111. package/src/cli/doctor.ts +14 -7
  112. package/src/cli/init.test.ts +1141 -0
  113. package/src/cli/init.ts +2 -1
  114. package/src/cli/mcp.ts +77 -1
  115. package/src/cli/run/adapters/copilot.ts +86 -58
  116. package/src/cli/run.ts +2 -2
  117. package/src/cli/stack-config-update.test.ts +210 -0
  118. package/src/cli/stack-config.ts +110 -37
  119. package/src/cli/types.ts +1 -1
  120. package/src/cli/update.ts +230 -23
  121. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  122. package/src/orchestrator/agents/api-designer.agent.md +1 -11
  123. package/src/orchestrator/agents/architect.agent.md +1 -9
  124. package/src/orchestrator/agents/content-engineer.agent.md +1 -5
  125. package/src/orchestrator/agents/copywriter.agent.md +1 -9
  126. package/src/orchestrator/agents/data-expert.agent.md +2 -6
  127. package/src/orchestrator/agents/database-engineer.agent.md +1 -6
  128. package/src/orchestrator/agents/developer.agent.md +2 -12
  129. package/src/orchestrator/agents/devops-expert.agent.md +1 -5
  130. package/src/orchestrator/agents/documentation-writer.agent.md +1 -4
  131. package/src/orchestrator/agents/performance-expert.agent.md +1 -5
  132. package/src/orchestrator/agents/release-manager.agent.md +1 -11
  133. package/src/orchestrator/agents/researcher.agent.md +1 -4
  134. package/src/orchestrator/agents/security-expert.agent.md +2 -7
  135. package/src/orchestrator/agents/seo-specialist.agent.md +1 -10
  136. package/src/orchestrator/agents/testing-expert.agent.md +2 -11
  137. package/src/orchestrator/agents/ui-ux-expert.agent.md +3 -10
  138. package/src/orchestrator/customizations/README.md +2 -1
  139. package/src/orchestrator/customizations/agents/skill-matrix.json +106 -0
  140. package/src/orchestrator/customizations/agents/skill-matrix.md +58 -121
  141. package/src/orchestrator/instructions/general.instructions.md +1 -1
  142. package/src/orchestrator/plugins/astro/SKILL.md +288 -0
  143. package/src/orchestrator/plugins/astro/config.ts +28 -0
  144. package/src/orchestrator/plugins/chrome-devtools/config.ts +2 -2
  145. package/src/orchestrator/plugins/contentful/config.ts +1 -1
  146. package/src/orchestrator/plugins/convex/config.ts +1 -1
  147. package/src/orchestrator/plugins/cypress/SKILL.md +145 -0
  148. package/src/orchestrator/plugins/cypress/config.ts +16 -0
  149. package/src/orchestrator/plugins/figma/SKILL.md +85 -0
  150. package/src/orchestrator/plugins/figma/config.ts +34 -0
  151. package/src/orchestrator/plugins/index.ts +20 -0
  152. package/src/orchestrator/plugins/jira/config.ts +2 -3
  153. package/src/orchestrator/plugins/linear/config.ts +2 -2
  154. package/src/orchestrator/plugins/netlify/SKILL.md +134 -0
  155. package/src/orchestrator/plugins/netlify/config.ts +31 -0
  156. package/src/orchestrator/plugins/nextjs/SKILL.md +376 -0
  157. package/src/orchestrator/plugins/nextjs/config.ts +36 -0
  158. package/src/orchestrator/plugins/nx/config.ts +2 -3
  159. package/src/orchestrator/plugins/playwright/SKILL.md +191 -0
  160. package/src/orchestrator/plugins/playwright/config.ts +26 -0
  161. package/src/orchestrator/plugins/prisma/SKILL.md +137 -0
  162. package/src/orchestrator/plugins/prisma/config.ts +26 -0
  163. package/src/orchestrator/plugins/resend/SKILL.md +187 -0
  164. package/src/orchestrator/plugins/resend/config.ts +47 -0
  165. package/src/orchestrator/plugins/sanity/config.ts +1 -2
  166. package/src/orchestrator/plugins/slack/config.ts +1 -1
  167. package/src/orchestrator/plugins/strapi/config.ts +1 -1
  168. package/src/orchestrator/plugins/supabase/config.ts +1 -2
  169. package/src/orchestrator/plugins/teams/config.ts +1 -2
  170. package/src/orchestrator/plugins/turborepo/SKILL.md +121 -0
  171. package/src/orchestrator/plugins/turborepo/config.ts +16 -0
  172. package/src/orchestrator/plugins/types.ts +7 -7
  173. package/src/orchestrator/plugins/vercel/SKILL.md +99 -0
  174. package/src/orchestrator/plugins/vercel/config.ts +2 -3
  175. package/src/orchestrator/plugins/vitest/SKILL.md +166 -0
  176. package/src/orchestrator/plugins/vitest/config.ts +16 -0
  177. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +6 -4
  178. package/src/orchestrator/prompts/create-skill.prompt.md +6 -7
  179. package/src/orchestrator/prompts/generate-task-spec.prompt.md +1 -1
  180. package/src/orchestrator/skills/agent-hooks/SKILL.md +2 -2
  181. package/src/orchestrator/skills/memory-merger/SKILL.md +1 -1
  182. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +0 -200
@@ -0,0 +1,376 @@
1
+ ---
2
+ name: nextjs-framework
3
+ description: "Next.js framework best practices covering App Router, server/client components, data fetching, caching, rendering strategies, middleware, configuration, and deployment. Use when creating or modifying Next.js pages, layouts, route handlers, Server Actions, or project configuration."
4
+ ---
5
+
6
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
7
+
8
+ # Next.js Framework
9
+
10
+ ## Project Structure
11
+
12
+ ```
13
+ ├── app/ # App Router (file-based routing)
14
+ │ ├── layout.tsx # Root layout (required)
15
+ │ ├── page.tsx # Home route → /
16
+ │ ├── loading.tsx # Loading UI (Suspense boundary)
17
+ │ ├── error.tsx # Error boundary (Client Component)
18
+ │ ├── not-found.tsx # 404 page
19
+ │ ├── global-error.tsx # Root error boundary
20
+ │ ├── (marketing)/ # Route group (no URL segment)
21
+ │ │ ├── about/page.tsx # → /about
22
+ │ │ └── blog/page.tsx # → /blog
23
+ │ ├── dashboard/
24
+ │ │ ├── layout.tsx # Nested layout
25
+ │ │ ├── page.tsx # → /dashboard
26
+ │ │ └── [id]/page.tsx # → /dashboard/:id
27
+ │ └── api/
28
+ │ └── users/route.ts # API route handler
29
+ ├── components/ # Shared React components
30
+ ├── lib/ # Utilities, helpers, server logic
31
+ ├── public/ # Static assets (served as-is)
32
+ ├── next.config.ts # Next.js configuration
33
+ ├── middleware.ts # Edge middleware
34
+ └── .env.local # Environment variables (not committed)
35
+ ```
36
+
37
+ - **Route Groups** `(name)` — organize routes without affecting the URL.
38
+ - **Private Folders** `_internal` — opt out of routing entirely.
39
+ - **Parallel Routes** `@modal` — render multiple pages in the same layout.
40
+ - **Intercepting Routes** `(.)photo` — intercept navigation to show modals.
41
+
42
+ ## Rendering Strategies
43
+
44
+ Next.js supports multiple rendering strategies per route:
45
+
46
+ | Strategy | When | How |
47
+ |----------|------|-----|
48
+ | **Static (SSG)** | Build time | Default for pages with no dynamic data |
49
+ | **Incremental Static Regeneration (ISR)** | Build + revalidation | `fetch` with `next: { revalidate: N }` or route segment config |
50
+ | **Server-Side Rendering (SSR)** | Every request | `export const dynamic = 'force-dynamic'` or dynamic functions (`cookies()`, `headers()`) |
51
+ | **Client-Side Rendering (CSR)** | Browser | `'use client'` components with `useEffect`/SWR |
52
+ | **Streaming** | Progressive | `<Suspense>` boundaries + `loading.tsx` |
53
+ | **Partial Prerendering (PPR)** | Build + streaming | Static shell with dynamic holes via `<Suspense>` |
54
+
55
+ ### Route Segment Config
56
+
57
+ Control per-route rendering behavior:
58
+
59
+ ```tsx
60
+ // app/dashboard/page.tsx
61
+ export const dynamic = 'force-dynamic'; // SSR every request
62
+ export const revalidate = 60; // ISR: revalidate every 60s
63
+ export const fetchCache = 'default-cache'; // Cache fetch requests
64
+ export const runtime = 'nodejs'; // 'nodejs' | 'edge'
65
+ ```
66
+
67
+ ## Server and Client Components
68
+
69
+ **Default: Server Components** — data fetching, heavy logic, non-interactive UI.
70
+
71
+ **Client Components** — add `'use client'` at top. Use for interactivity, state, browser APIs.
72
+
73
+ ### Decision Table
74
+
75
+ | Need | Component Type | Why |
76
+ |------|---------------|-----|
77
+ | Fetch data at request time | Server | Direct DB/API access, no client waterfall |
78
+ | Read cookies/headers | Server | Available only on the server |
79
+ | Interactive UI (clicks, inputs) | Client | Requires event handlers |
80
+ | Use `useState` / `useEffect` | Client | React hooks need client runtime |
81
+ | Access browser APIs (localStorage, geolocation) | Client | Not available on server |
82
+ | Render static/non-interactive content | Server | Smaller bundle, faster paint |
83
+ | Show loading spinners for async children | Server (with `<Suspense>`) | Streams HTML progressively |
84
+
85
+ ### Critical Rule
86
+
87
+ **Never use `next/dynamic` with `{ ssr: false }` inside a Server Component.** Move client-only logic into a dedicated `'use client'` component, then import it normally.
88
+
89
+ ## Data Fetching
90
+
91
+ ### Server-Side Fetching
92
+
93
+ Fetch directly in `async` Server Components. Next.js deduplicates identical `fetch` calls.
94
+
95
+ ```tsx
96
+ export default async function ProjectsPage() {
97
+ const projects = await fetch('https://api.example.com/projects', {
98
+ next: { revalidate: 60 },
99
+ }).then((res) => res.json());
100
+ return <ul>{projects.map((p: { id: string; name: string }) => <li key={p.id}>{p.name}</li>)}</ul>;
101
+ }
102
+ ```
103
+
104
+ ### Server Actions (Mutations)
105
+
106
+ Define with `'use server'`. Call from Client Components via `action` or `startTransition`.
107
+
108
+ ```tsx
109
+ // lib/actions.ts
110
+ 'use server';
111
+ import { revalidatePath } from 'next/cache';
112
+ export async function createItem(formData: FormData) {
113
+ const name = formData.get('name') as string;
114
+ await db.items.create({ data: { name } });
115
+ revalidatePath('/items');
116
+ }
117
+ ```
118
+
119
+ ```tsx
120
+ // components/CreateItemForm.tsx
121
+ 'use client';
122
+ import { createItem } from '@/lib/actions';
123
+ export default function CreateItemForm() {
124
+ return <form action={createItem}><input name="name" required /><button type="submit">Add</button></form>;
125
+ }
126
+ ```
127
+
128
+ ### Parallel Data Fetching
129
+
130
+ Initiate independent fetches simultaneously — never `await` sequentially.
131
+
132
+ ```tsx
133
+ export default async function DashboardPage() {
134
+ const [metrics, activity] = await Promise.all([getMetrics(), getRecentActivity()]);
135
+ return <><MetricsPanel data={metrics} /><ActivityFeed items={activity} /></>;
136
+ }
137
+ ```
138
+
139
+ ## Caching and Revalidation
140
+
141
+ | Mechanism | Scope | How to Use |
142
+ |-----------|-------|------------|
143
+ | **Request Memoization** | Per-request | Automatic deduplication of identical `fetch` calls |
144
+ | **Data Cache** | Cross-request | `fetch` results cached by default; opt out with `cache: 'no-store'` |
145
+ | **Full Route Cache** | Build time | Static routes cached as HTML + RSC payload |
146
+ | **Router Cache** | Client-side | Prefetched and visited routes cached in browser |
147
+
148
+ ### Revalidation
149
+
150
+ ```tsx
151
+ // Time-based — revalidate every 60 seconds
152
+ fetch(url, { next: { revalidate: 60 } });
153
+
154
+ // On-demand — revalidate by path or tag
155
+ import { revalidatePath, revalidateTag } from 'next/cache';
156
+ revalidatePath('/blog');
157
+ revalidateTag('posts');
158
+
159
+ // Tag a fetch for on-demand revalidation
160
+ fetch(url, { next: { tags: ['posts'] } });
161
+ ```
162
+
163
+ ## Routing
164
+
165
+ ### File Conventions
166
+
167
+ | File | Purpose |
168
+ |------|---------|
169
+ | `page.tsx` | Route UI (makes segment publicly accessible) |
170
+ | `layout.tsx` | Shared layout (wraps children, persists across navigation) |
171
+ | `template.tsx` | Like layout but re-mounts on navigation |
172
+ | `loading.tsx` | Loading UI (automatic Suspense boundary) |
173
+ | `error.tsx` | Error UI (Client Component, automatic error boundary) |
174
+ | `not-found.tsx` | 404 UI |
175
+ | `route.ts` | API endpoint (no UI) |
176
+ | `default.tsx` | Fallback for parallel routes |
177
+
178
+ ### Dynamic Routes
179
+
180
+ ```
181
+ app/blog/[slug]/page.tsx → /blog/:slug
182
+ app/shop/[...slug]/page.tsx → /shop/:slug+ (catch-all)
183
+ app/shop/[[...slug]]/page.tsx → /shop or /shop/:slug+ (optional catch-all)
184
+ ```
185
+
186
+ ### Route Handlers (API Routes)
187
+
188
+ ```ts
189
+ // app/api/users/route.ts
190
+ import { NextRequest, NextResponse } from 'next/server';
191
+
192
+ export async function GET(request: NextRequest) {
193
+ const { searchParams } = request.nextUrl;
194
+ const query = searchParams.get('q');
195
+ const users = await findUsers(query);
196
+ return NextResponse.json(users);
197
+ }
198
+
199
+ export async function POST(request: NextRequest) {
200
+ const body = await request.json();
201
+ const user = await createUser(body);
202
+ return NextResponse.json(user, { status: 201 });
203
+ }
204
+ ```
205
+
206
+ ## Error Handling
207
+
208
+ ```tsx
209
+ // app/dashboard/error.tsx — must be a Client Component
210
+ 'use client';
211
+ export default function DashboardError({ error, reset }: { error: Error; reset: () => void }) {
212
+ return <div role="alert"><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
213
+ }
214
+ ```
215
+
216
+ ```tsx
217
+ // app/projects/[id]/page.tsx — trigger not-found boundary
218
+ import { notFound } from 'next/navigation';
219
+ export default async function ProjectPage({ params }: { params: { id: string } }) {
220
+ const project = await getProject(params.id);
221
+ if (!project) notFound();
222
+ return <h1>{project.name}</h1>;
223
+ }
224
+ ```
225
+
226
+ ## Middleware
227
+
228
+ Runs at the Edge before every matched request. Use for auth, redirects, rewrites, headers.
229
+
230
+ ```ts
231
+ import { NextResponse, type NextRequest } from 'next/server';
232
+
233
+ export function middleware(request: NextRequest) {
234
+ const token = request.cookies.get('session')?.value;
235
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
236
+ return NextResponse.redirect(new URL('/login', request.url));
237
+ }
238
+ // Add custom headers
239
+ const response = NextResponse.next();
240
+ response.headers.set('x-request-id', crypto.randomUUID());
241
+ return response;
242
+ }
243
+
244
+ export const config = { matcher: ['/dashboard/:path*', '/settings/:path*'] };
245
+ ```
246
+
247
+ ## Configuration
248
+
249
+ ### `next.config.ts`
250
+
251
+ ```ts
252
+ import type { NextConfig } from 'next';
253
+
254
+ const nextConfig: NextConfig = {
255
+ reactStrictMode: true,
256
+ images: {
257
+ remotePatterns: [
258
+ { protocol: 'https', hostname: 'cdn.example.com' },
259
+ ],
260
+ },
261
+ experimental: {
262
+ ppr: true, // Partial Prerendering
263
+ typedRoutes: true, // Type-safe <Link> hrefs
264
+ },
265
+ // Redirects, rewrites, headers
266
+ async redirects() {
267
+ return [{ source: '/old-path', destination: '/new-path', permanent: true }];
268
+ },
269
+ };
270
+
271
+ export default nextConfig;
272
+ ```
273
+
274
+ ### Environment Variables
275
+
276
+ | Prefix | Available in | Use Case |
277
+ |--------|-------------|----------|
278
+ | `NEXT_PUBLIC_` | Server + Client | Public values (API base URLs, feature flags) |
279
+ | No prefix | Server only | Secrets (DB URLs, API keys, tokens) |
280
+
281
+ Files loaded (in priority order): `.env.local`, `.env.development` / `.env.production`, `.env`.
282
+
283
+ ## Image and Font Optimization
284
+
285
+ ### Images
286
+
287
+ ```tsx
288
+ import Image from 'next/image';
289
+ import heroImg from '@/public/hero.jpg';
290
+
291
+ // Local image — auto width/height from import
292
+ <Image src={heroImg} alt="Hero" priority />
293
+
294
+ // Remote image — must specify dimensions
295
+ <Image src="https://cdn.example.com/photo.jpg" alt="Photo" width={800} height={600} />
296
+ ```
297
+
298
+ ### Fonts
299
+
300
+ ```tsx
301
+ // app/layout.tsx
302
+ import { Inter } from 'next/font/google';
303
+ const inter = Inter({ subsets: ['latin'], display: 'swap' });
304
+
305
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
306
+ return <html lang="en" className={inter.className}><body>{children}</body></html>;
307
+ }
308
+ ```
309
+
310
+ ## Metadata and SEO
311
+
312
+ ```tsx
313
+ // app/layout.tsx — static metadata
314
+ import type { Metadata } from 'next';
315
+
316
+ export const metadata: Metadata = {
317
+ title: { default: 'My App', template: '%s | My App' },
318
+ description: 'App description',
319
+ openGraph: { title: 'My App', description: 'App description', type: 'website' },
320
+ };
321
+ ```
322
+
323
+ ```tsx
324
+ // app/blog/[slug]/page.tsx — dynamic metadata
325
+ import type { Metadata } from 'next';
326
+
327
+ export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
328
+ const post = await getPost(params.slug);
329
+ return { title: post.title, description: post.excerpt };
330
+ }
331
+ ```
332
+
333
+ ## Performance Patterns
334
+
335
+ - **Default to Server Components** — smaller client bundles, faster paint.
336
+ - **`<Suspense>` + `loading.tsx`** — stream content progressively; never block the whole page.
337
+ - **`Promise.all()`** — parallel data fetching for independent data.
338
+ - **Dynamic imports** — lazy-load heavy Client Components with `next/dynamic`.
339
+ - **`<Image>`** — automatic lazy loading, responsive sizing, format conversion.
340
+ - **`next/font`** — zero layout shift, self-hosted fonts.
341
+ - **Route segment config** — fine-tune caching and rendering per route.
342
+
343
+ ## Deployment
344
+
345
+ Next.js deploys to multiple targets:
346
+
347
+ | Target | Config | Notes |
348
+ |--------|--------|-------|
349
+ | **Vercel** | Zero-config | Full feature support including Edge, ISR, Middleware |
350
+ | **Node.js server** | `output: 'standalone'` | Minimal `standalone/` folder with server |
351
+ | **Docker** | `output: 'standalone'` | Copy `.next/standalone` + `.next/static` + `public` |
352
+ | **Static export** | `output: 'export'` | No server features (no SSR, API routes, middleware) |
353
+
354
+ ## Component Practices & Naming
355
+
356
+ - PascalCase for component files/exports. camelCase for hooks.
357
+ - Shared components in `components/`. Route-specific components co-located in route folder.
358
+ - TypeScript interfaces for props. Explicit types and defaults.
359
+ - Co-locate tests with components.
360
+ - Folders: `kebab-case`. Types/Interfaces: `PascalCase`. Constants: `UPPER_SNAKE_CASE`.
361
+
362
+ ## Anti-Patterns
363
+
364
+ | Anti-Pattern | Why It's Wrong | Do This Instead |
365
+ |-------------|---------------|-----------------|
366
+ | `'use client'` on every component | Bloats JS bundle, defeats RSC benefits | Default to Server Components; add `'use client'` only when needed |
367
+ | Sequential `await` for independent data | Creates a waterfall, slows page load | Use `Promise.all()` for parallel fetches |
368
+ | `next/dynamic` with `ssr: false` in Server Components | Build/runtime crash | Extract to a Client Component, import normally |
369
+ | Fetching in `useEffect` when server fetch works | Extra client roundtrip, loading flash | Fetch in the Server Component or use Server Actions |
370
+ | Giant `layout.tsx` with all providers | Hard to test, couples unrelated concerns | Split providers into a `Providers` Client Component |
371
+ | Catching errors without `error.tsx` | Unhandled errors crash the page | Add `error.tsx` per route segment |
372
+ | Hardcoding secrets in source files | Security risk, leaks in version control | Use `.env.local` and `process.env` |
373
+ | Skipping `loading.tsx` / `<Suspense>` | Blank screen while data loads | Add `loading.tsx` or wrap in `<Suspense>` |
374
+ | Using `getServerSideProps` / `getStaticProps` | Legacy Pages Router patterns | Use App Router with `async` Server Components |
375
+ | Ignoring `next.config.ts` for images | Remote images blocked by default | Configure `images.remotePatterns` |
376
+ | Missing `metadata` exports | Poor SEO, no social previews | Export `metadata` or `generateMetadata` per page |
@@ -0,0 +1,36 @@
1
+ import type { PluginConfig } from '../types.js';
2
+
3
+ export const config: PluginConfig = {
4
+ id: 'nextjs',
5
+ name: 'Next.js',
6
+ category: 'tech',
7
+ subCategory: 'framework',
8
+ label: 'Next.js',
9
+ hint: 'React framework with App Router, Server Components, and MCP devtools',
10
+ skillName: 'nextjs-framework',
11
+ mcpServerKey: 'next-devtools',
12
+ mcpConfig: {
13
+ type: 'stdio',
14
+ command: 'npx',
15
+ args: ['-y', 'next-devtools-mcp@latest'],
16
+ },
17
+ authType: 'none',
18
+ envVars: [],
19
+ agentToolMap: {
20
+ 'developer': [
21
+ 'next-devtools/get_errors', 'next-devtools/get_logs',
22
+ 'next-devtools/get_page_metadata', 'next-devtools/get_project_metadata',
23
+ 'next-devtools/get_server_action_by_id',
24
+ ],
25
+ 'performance-expert': [
26
+ 'next-devtools/get_errors', 'next-devtools/get_logs',
27
+ 'next-devtools/get_page_metadata', 'next-devtools/get_project_metadata',
28
+ ],
29
+ 'testing-expert': [
30
+ 'next-devtools/get_errors', 'next-devtools/get_logs',
31
+ ],
32
+ },
33
+ docsUrl: 'https://www.opencastle.dev/docs/plugins#nextjs',
34
+ officialDocs: 'https://nextjs.org/docs',
35
+ mcpPackage: 'next-devtools-mcp',
36
+ };
@@ -4,7 +4,7 @@ export const config: PluginConfig = {
4
4
  id: 'nx',
5
5
  name: 'NX',
6
6
  category: 'tech',
7
- subCategory: 'monorepo',
7
+ subCategory: 'codebase-tool',
8
8
  label: 'NX',
9
9
  hint: 'Monorepo build system',
10
10
  skillName: 'nx-workspace',
@@ -23,7 +23,6 @@ export const config: PluginConfig = {
23
23
  'performance-expert': ['nx-mcp-server/nx_project_details', 'nx-mcp-server/nx_workspace'],
24
24
  'release-manager': ['nx-mcp-server/nx_project_details', 'nx-mcp-server/nx_workspace', 'nx-mcp-server/nx_workspace_path'],
25
25
  },
26
- docsUrl: null,
26
+ docsUrl: 'https://www.opencastle.dev/docs/plugins#nx',
27
27
  officialDocs: 'https://nx.dev/getting-started/intro',
28
- mcpPackage: null,
29
28
  };
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: playwright-testing
3
+ description: "Playwright E2E testing patterns, cross-browser configuration, page objects, and CI setup. Use when writing E2E tests, visual regression tests, or configuring Playwright in CI pipelines."
4
+ ---
5
+
6
+ <!-- ⚠️ This file is managed by OpenCastle. Edits will be overwritten on update. Customize in the .github/customizations/ directory instead. -->
7
+
8
+ # Playwright Testing
9
+
10
+ Playwright-specific E2E testing patterns. For project-specific test configuration and breakpoints, see [testing-config.md](../../customizations/stack/testing-config.md).
11
+
12
+ ## Commands
13
+
14
+ ```bash
15
+ npx playwright test # Run all tests
16
+ npx playwright test --ui # Open interactive UI mode
17
+ npx playwright test --headed # Run with visible browsers
18
+ npx playwright test auth.spec.ts # Run specific spec
19
+ npx playwright test --project=chromium # Run in specific browser
20
+ npx playwright test --grep "login" # Filter by test name
21
+ npx playwright test --debug # Step-through debugging
22
+ npx playwright codegen http://localhost:3000 # Generate tests from actions
23
+ npx playwright show-report # View HTML test report
24
+ npx playwright install # Install browsers
25
+ ```
26
+
27
+ ## Test Structure
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
+ ```
43
+
44
+ ## Writing Tests
45
+
46
+ ### Basic Test Pattern
47
+
48
+ ```typescript
49
+ import { test, expect } from '@playwright/test';
50
+
51
+ test.describe('Login', () => {
52
+ test.beforeEach(async ({ page }) => {
53
+ await page.goto('/login');
54
+ });
55
+
56
+ test('should log in with valid credentials', async ({ page }) => {
57
+ await page.getByTestId('email-input').fill('user@example.com');
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');
71
+ });
72
+ });
73
+ ```
74
+
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
+ ### Custom Fixtures
107
+
108
+ ```typescript
109
+ // tests/fixtures/auth.fixture.ts
110
+ import { test as base } from '@playwright/test';
111
+
112
+ type AuthFixtures = {
113
+ authenticatedPage: Page;
114
+ };
115
+
116
+ export const test = base.extend<AuthFixtures>({
117
+ authenticatedPage: async ({ page }, use) => {
118
+ await page.goto('/login');
119
+ await page.getByTestId('email-input').fill('test@example.com');
120
+ await page.getByTestId('password-input').fill('password');
121
+ await page.getByTestId('login-button').click();
122
+ await page.waitForURL('**/dashboard');
123
+ await use(page);
124
+ },
125
+ });
126
+ ```
127
+
128
+ ## Locator Strategy
129
+
130
+ Use Playwright's built-in locators in priority order:
131
+
132
+ 1. `page.getByTestId()` — most resilient
133
+ 2. `page.getByRole()` — accessible, meaningful
134
+ 3. `page.getByLabel()` — for form elements
135
+ 4. `page.getByText()` — for unique visible text
136
+ 5. `page.locator()` with CSS — last resort
137
+
138
+ ## Configuration (playwright.config.ts)
139
+
140
+ ```typescript
141
+ import { defineConfig, devices } from '@playwright/test';
142
+
143
+ export default defineConfig({
144
+ testDir: './tests',
145
+ fullyParallel: true,
146
+ forbidOnly: !!process.env.CI,
147
+ retries: process.env.CI ? 2 : 0,
148
+ workers: process.env.CI ? 1 : undefined,
149
+ reporter: [['html'], ['list']],
150
+ use: {
151
+ baseURL: 'http://localhost:3000',
152
+ trace: 'on-first-retry',
153
+ screenshot: 'only-on-failure',
154
+ },
155
+ projects: [
156
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
157
+ { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
158
+ { name: 'webkit', use: { ...devices['Desktop Safari'] } },
159
+ { name: 'mobile', use: { ...devices['iPhone 14'] } },
160
+ ],
161
+ webServer: {
162
+ command: 'npm run dev',
163
+ url: 'http://localhost:3000',
164
+ reuseExistingServer: !process.env.CI,
165
+ },
166
+ });
167
+ ```
168
+
169
+ ## MCP Tools
170
+
171
+ The Playwright MCP server enables AI agents to interact with browsers directly:
172
+
173
+ | Tool | Purpose |
174
+ |------|---------|
175
+ | `playwright/navigate` | Navigate to a URL |
176
+ | `playwright/screenshot` | Take page screenshots |
177
+ | `playwright/click` | Click elements |
178
+ | `playwright/fill` | Fill form inputs |
179
+ | `playwright/evaluate` | Execute JavaScript in browser |
180
+ | `playwright/expect` | Assert page state |
181
+
182
+ ## Best Practices
183
+
184
+ - Use `test.describe` to group related tests
185
+ - Use `test.beforeEach` for common setup — keep tests independent
186
+ - Prefer `getByTestId` and `getByRole` over CSS selectors
187
+ - Use `expect(locator).toBeVisible()` before interacting
188
+ - Use `page.waitForURL()` or `page.waitForResponse()` instead of arbitrary waits
189
+ - Run tests in parallel (`fullyParallel: true`) for speed
190
+ - Use `trace: 'on-first-retry'` to debug flaky tests
191
+ - Use `codegen` to bootstrap tests, then refactor into page objects
@@ -0,0 +1,26 @@
1
+ import type { PluginConfig } from '../types.js';
2
+
3
+ export const config: PluginConfig = {
4
+ id: 'playwright',
5
+ name: 'Playwright',
6
+ category: 'tech',
7
+ subCategory: 'e2e-testing',
8
+ label: 'Playwright',
9
+ hint: 'Cross-browser E2E testing by Microsoft',
10
+ skillName: 'playwright-testing',
11
+ mcpServerKey: 'Playwright',
12
+ mcpConfig: {
13
+ type: 'stdio',
14
+ command: 'npx',
15
+ args: ['-y', '@playwright/mcp@latest'],
16
+ },
17
+ authType: 'none',
18
+ envVars: [],
19
+ agentToolMap: {
20
+ 'testing-expert': ['playwright/*'],
21
+ 'ui-ux-expert': ['playwright/*'],
22
+ },
23
+ docsUrl: 'https://www.opencastle.dev/docs/plugins#playwright',
24
+ officialDocs: 'https://playwright.dev/docs/intro',
25
+ mcpPackage: '@playwright/mcp',
26
+ };