@girardmedia/bootspring 3.3.2 → 3.4.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.
Files changed (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,340 @@
1
+ ---
2
+ name: video-processing
3
+ description: Video processing patterns with FFmpeg for transcoding, HLS streaming, thumbnail generation, upload handling, and progress tracking.
4
+ ---
5
+
6
+ # Video Processing Patterns
7
+
8
+ ## When to Use
9
+ Apply video processing patterns when your application handles user-uploaded videos, needs adaptive streaming, generates video thumbnails, or transcodes between formats. FFmpeg (via fluent-ffmpeg) is the standard tool for server-side video work. These patterns cover transcoding, HLS/DASH adaptive streaming, thumbnail extraction, chunked upload handling, and progress reporting. Always process videos asynchronously via job queues.
10
+
11
+ ## How It Works
12
+
13
+ ### FFmpeg Transcoding
14
+
15
+ ```typescript
16
+ // src/video/transcoder.ts
17
+ import ffmpeg from 'fluent-ffmpeg';
18
+ import path from 'path';
19
+
20
+ interface TranscodeOptions {
21
+ inputPath: string;
22
+ outputPath: string;
23
+ width?: number;
24
+ height?: number;
25
+ videoBitrate?: string;
26
+ audioBitrate?: string;
27
+ format?: 'mp4' | 'webm';
28
+ onProgress?: (percent: number) => void;
29
+ }
30
+
31
+ export function transcodeVideo(options: TranscodeOptions): Promise<string> {
32
+ return new Promise((resolve, reject) => {
33
+ let command = ffmpeg(options.inputPath)
34
+ .videoCodec(options.format === 'webm' ? 'libvpx-vp9' : 'libx264')
35
+ .audioCodec(options.format === 'webm' ? 'libopus' : 'aac')
36
+ .audioBitrate(options.audioBitrate ?? '128k')
37
+ .videoBitrate(options.videoBitrate ?? '2500k')
38
+ .format(options.format ?? 'mp4');
39
+
40
+ if (options.width || options.height) {
41
+ const w = options.width ?? -2; // -2 = auto, divisible by 2
42
+ const h = options.height ?? -2;
43
+ command = command.size(`${w}x${h}`);
44
+ }
45
+
46
+ if (options.format === 'mp4') {
47
+ command = command.outputOptions([
48
+ '-preset', 'fast',
49
+ '-crf', '23',
50
+ '-movflags', '+faststart', // optimize for web streaming
51
+ '-pix_fmt', 'yuv420p', // compatibility
52
+ ]);
53
+ }
54
+
55
+ command
56
+ .on('progress', (progress) => {
57
+ options.onProgress?.(Math.round(progress.percent ?? 0));
58
+ })
59
+ .on('end', () => resolve(options.outputPath))
60
+ .on('error', (err) => reject(new Error(`Transcode failed: ${err.message}`)))
61
+ .save(options.outputPath);
62
+ });
63
+ }
64
+ ```
65
+
66
+ ### HLS Adaptive Streaming
67
+
68
+ ```typescript
69
+ // src/video/hls-generator.ts
70
+ import ffmpeg from 'fluent-ffmpeg';
71
+ import fs from 'fs/promises';
72
+ import path from 'path';
73
+
74
+ interface HlsVariant {
75
+ width: number;
76
+ height: number;
77
+ videoBitrate: string;
78
+ audioBitrate: string;
79
+ label: string;
80
+ }
81
+
82
+ const variants: HlsVariant[] = [
83
+ { width: 426, height: 240, videoBitrate: '400k', audioBitrate: '64k', label: '240p' },
84
+ { width: 640, height: 360, videoBitrate: '800k', audioBitrate: '96k', label: '360p' },
85
+ { width: 854, height: 480, videoBitrate: '1400k', audioBitrate: '128k', label: '480p' },
86
+ { width: 1280, height: 720, videoBitrate: '2800k', audioBitrate: '128k', label: '720p' },
87
+ { width: 1920, height: 1080, videoBitrate: '5000k', audioBitrate: '192k', label: '1080p' },
88
+ ];
89
+
90
+ export async function generateHls(
91
+ inputPath: string,
92
+ outputDir: string,
93
+ onProgress?: (variant: string, percent: number) => void
94
+ ): Promise<string> {
95
+ await fs.mkdir(outputDir, { recursive: true });
96
+
97
+ // Generate each variant
98
+ for (const variant of variants) {
99
+ const variantDir = path.join(outputDir, variant.label);
100
+ await fs.mkdir(variantDir, { recursive: true });
101
+
102
+ await new Promise<void>((resolve, reject) => {
103
+ ffmpeg(inputPath)
104
+ .videoCodec('libx264')
105
+ .audioCodec('aac')
106
+ .size(`${variant.width}x${variant.height}`)
107
+ .videoBitrate(variant.videoBitrate)
108
+ .audioBitrate(variant.audioBitrate)
109
+ .outputOptions([
110
+ '-preset', 'fast',
111
+ '-crf', '23',
112
+ '-g', '48', // keyframe every 2s at 24fps
113
+ '-keyint_min', '48',
114
+ '-sc_threshold', '0',
115
+ '-hls_time', '6', // 6-second segments
116
+ '-hls_list_size', '0', // keep all segments
117
+ '-hls_segment_filename', path.join(variantDir, 'segment_%03d.ts'),
118
+ '-f', 'hls',
119
+ ])
120
+ .on('progress', (p) => onProgress?.(variant.label, Math.round(p.percent ?? 0)))
121
+ .on('end', () => resolve())
122
+ .on('error', (err) => reject(err))
123
+ .save(path.join(variantDir, 'playlist.m3u8'));
124
+ });
125
+ }
126
+
127
+ // Generate master playlist
128
+ const masterPlaylist = generateMasterPlaylist(variants);
129
+ const masterPath = path.join(outputDir, 'master.m3u8');
130
+ await fs.writeFile(masterPath, masterPlaylist);
131
+
132
+ return masterPath;
133
+ }
134
+
135
+ function generateMasterPlaylist(variants: HlsVariant[]): string {
136
+ let content = '#EXTM3U\n#EXT-X-VERSION:3\n\n';
137
+
138
+ for (const v of variants) {
139
+ const bandwidth = parseInt(v.videoBitrate) * 1000;
140
+ content += `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${v.width}x${v.height}\n`;
141
+ content += `${v.label}/playlist.m3u8\n\n`;
142
+ }
143
+
144
+ return content;
145
+ }
146
+ ```
147
+
148
+ ### Thumbnail Generation
149
+
150
+ ```typescript
151
+ // src/video/thumbnails.ts
152
+ import ffmpeg from 'fluent-ffmpeg';
153
+
154
+ export async function generateThumbnails(
155
+ inputPath: string,
156
+ outputDir: string,
157
+ count: number = 5
158
+ ): Promise<string[]> {
159
+ return new Promise((resolve, reject) => {
160
+ const filenames: string[] = [];
161
+
162
+ ffmpeg(inputPath)
163
+ .on('filenames', (names) => filenames.push(...names))
164
+ .on('end', () => resolve(filenames.map((f) => path.join(outputDir, f))))
165
+ .on('error', (err) => reject(err))
166
+ .screenshots({
167
+ count,
168
+ folder: outputDir,
169
+ filename: 'thumb_%i.jpg',
170
+ size: '640x360',
171
+ });
172
+ });
173
+ }
174
+
175
+ // Generate a single thumbnail at specific time
176
+ export async function generateThumbnailAtTime(
177
+ inputPath: string,
178
+ outputPath: string,
179
+ timeSeconds: number
180
+ ): Promise<string> {
181
+ return new Promise((resolve, reject) => {
182
+ ffmpeg(inputPath)
183
+ .seekInput(timeSeconds)
184
+ .frames(1)
185
+ .size('640x360')
186
+ .on('end', () => resolve(outputPath))
187
+ .on('error', (err) => reject(err))
188
+ .save(outputPath);
189
+ });
190
+ }
191
+
192
+ // Generate animated GIF preview
193
+ export async function generateGifPreview(
194
+ inputPath: string,
195
+ outputPath: string,
196
+ startTime: number = 0,
197
+ duration: number = 3
198
+ ): Promise<string> {
199
+ return new Promise((resolve, reject) => {
200
+ ffmpeg(inputPath)
201
+ .seekInput(startTime)
202
+ .duration(duration)
203
+ .outputOptions([
204
+ '-vf', 'fps=10,scale=320:-1:flags=lanczos',
205
+ '-loop', '0',
206
+ ])
207
+ .on('end', () => resolve(outputPath))
208
+ .on('error', (err) => reject(err))
209
+ .save(outputPath);
210
+ });
211
+ }
212
+ ```
213
+
214
+ ### Video Metadata Extraction
215
+
216
+ ```typescript
217
+ // src/video/metadata.ts
218
+ import ffmpeg from 'fluent-ffmpeg';
219
+
220
+ export interface VideoMetadata {
221
+ duration: number;
222
+ width: number;
223
+ height: number;
224
+ fps: number;
225
+ bitrate: number;
226
+ codec: string;
227
+ audioCodec: string;
228
+ fileSize: number;
229
+ }
230
+
231
+ export function getVideoMetadata(inputPath: string): Promise<VideoMetadata> {
232
+ return new Promise((resolve, reject) => {
233
+ ffmpeg.ffprobe(inputPath, (err, data) => {
234
+ if (err) return reject(err);
235
+
236
+ const videoStream = data.streams.find((s) => s.codec_type === 'video');
237
+ const audioStream = data.streams.find((s) => s.codec_type === 'audio');
238
+
239
+ if (!videoStream) return reject(new Error('No video stream found'));
240
+
241
+ resolve({
242
+ duration: data.format.duration ?? 0,
243
+ width: videoStream.width ?? 0,
244
+ height: videoStream.height ?? 0,
245
+ fps: eval(videoStream.r_frame_rate ?? '0') || 0,
246
+ bitrate: parseInt(String(data.format.bit_rate ?? '0')),
247
+ codec: videoStream.codec_name ?? 'unknown',
248
+ audioCodec: audioStream?.codec_name ?? 'none',
249
+ fileSize: data.format.size ?? 0,
250
+ });
251
+ });
252
+ });
253
+ }
254
+ ```
255
+
256
+ ### Chunked Upload with Resumability
257
+
258
+ ```typescript
259
+ // src/video/upload-handler.ts
260
+ import { Router } from 'express';
261
+ import fs from 'fs/promises';
262
+ import path from 'path';
263
+
264
+ const router = Router();
265
+ const UPLOAD_DIR = '/tmp/video-uploads';
266
+
267
+ // Initialize upload
268
+ router.post('/api/videos/upload/init', async (req, res) => {
269
+ const { filename, fileSize, mimeType } = req.body;
270
+ const uploadId = crypto.randomUUID();
271
+ const chunkSize = 5 * 1024 * 1024; // 5MB chunks
272
+ const totalChunks = Math.ceil(fileSize / chunkSize);
273
+
274
+ await fs.mkdir(path.join(UPLOAD_DIR, uploadId), { recursive: true });
275
+ await db.query(
276
+ 'INSERT INTO uploads (id, filename, file_size, mime_type, total_chunks, status) VALUES ($1,$2,$3,$4,$5,$6)',
277
+ [uploadId, filename, fileSize, mimeType, totalChunks, 'uploading']
278
+ );
279
+
280
+ res.json({ uploadId, chunkSize, totalChunks });
281
+ });
282
+
283
+ // Upload a chunk
284
+ router.put('/api/videos/upload/:uploadId/chunk/:index', async (req, res) => {
285
+ const { uploadId, index } = req.params;
286
+ const chunkPath = path.join(UPLOAD_DIR, uploadId, `chunk_${index.padStart(5, '0')}`);
287
+
288
+ const chunks: Buffer[] = [];
289
+ for await (const chunk of req) chunks.push(chunk);
290
+ await fs.writeFile(chunkPath, Buffer.concat(chunks));
291
+
292
+ res.json({ received: parseInt(index) });
293
+ });
294
+
295
+ // Finalize upload — merge chunks and queue processing
296
+ router.post('/api/videos/upload/:uploadId/finalize', async (req, res) => {
297
+ const { uploadId } = req.params;
298
+ const uploadDir = path.join(UPLOAD_DIR, uploadId);
299
+ const chunks = (await fs.readdir(uploadDir)).sort();
300
+ const outputPath = path.join(UPLOAD_DIR, `${uploadId}.mp4`);
301
+
302
+ // Merge chunks
303
+ const writeStream = require('fs').createWriteStream(outputPath);
304
+ for (const chunk of chunks) {
305
+ const data = await fs.readFile(path.join(uploadDir, chunk));
306
+ writeStream.write(data);
307
+ }
308
+ writeStream.end();
309
+
310
+ // Clean up chunks
311
+ await fs.rm(uploadDir, { recursive: true });
312
+
313
+ // Queue processing job
314
+ await videoQueue.add('process', { uploadId, inputPath: outputPath });
315
+
316
+ res.json({ uploadId, status: 'processing' });
317
+ });
318
+
319
+ export default router;
320
+ ```
321
+
322
+ ## Examples
323
+
324
+ | Operation | Tool | Output |
325
+ |-----------|------|--------|
326
+ | Transcode to MP4 | fluent-ffmpeg | Web-optimized MP4 (faststart) |
327
+ | Adaptive streaming | HLS generator | Master playlist + segment files |
328
+ | Thumbnail grid | ffmpeg screenshots | 5 JPEG thumbnails at intervals |
329
+ | GIF preview | ffmpeg filter | 3-second looping GIF |
330
+ | Metadata | ffprobe | Duration, resolution, codec info |
331
+
332
+ ## Checklist
333
+ - [ ] Video processing runs asynchronously via job queue, not in request handler
334
+ - [ ] FFmpeg outputs use `-movflags +faststart` for web MP4 files
335
+ - [ ] HLS segments are 6 seconds with closed GOPs for clean seeking
336
+ - [ ] Multiple quality variants generated for adaptive streaming
337
+ - [ ] Thumbnails generated at evenly spaced intervals
338
+ - [ ] Chunked upload supports resumption on connection failure
339
+ - [ ] Progress reported to client via WebSocket or polling endpoint
340
+ - [ ] Temporary files cleaned up after processing completes
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: vue-patterns
3
+ description: Vue.js patterns for Composition API, composables, Pinia stores, slots, teleport, and transitions.
4
+ ---
5
+
6
+ # Vue.js Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when building Vue 3 applications with the Composition API.
11
+ Use this skill for writing composables, managing state with Pinia, designing
12
+ reusable components with slots, rendering outside the DOM tree with Teleport,
13
+ and adding animations with transitions.
14
+
15
+ ## How It Works
16
+
17
+ ### Composition API with `<script setup>`
18
+
19
+ Use `<script setup>` for all components. It provides better TypeScript inference,
20
+ less boilerplate, and automatic component/directive registration.
21
+
22
+ ```vue
23
+ <script setup lang="ts">
24
+ import { ref, computed, onMounted } from 'vue'
25
+ import { useUserStore } from '@/stores/user'
26
+
27
+ const props = defineProps<{
28
+ projectId: string
29
+ editable?: boolean
30
+ }>()
31
+
32
+ const emit = defineEmits<{
33
+ save: [data: ProjectData]
34
+ cancel: []
35
+ }>()
36
+
37
+ const userStore = useUserStore()
38
+ const title = ref('')
39
+ const isValid = computed(() => title.value.length >= 3)
40
+
41
+ onMounted(async () => {
42
+ const project = await fetchProject(props.projectId)
43
+ title.value = project.title
44
+ })
45
+
46
+ function handleSave() {
47
+ if (isValid.value) {
48
+ emit('save', { title: title.value })
49
+ }
50
+ }
51
+ </script>
52
+ ```
53
+
54
+ ### Composables
55
+
56
+ Extract reusable reactive logic into composable functions. Name them `use*`.
57
+ Return reactive refs and methods. Accept refs as arguments for reactivity.
58
+
59
+ ```typescript
60
+ // composables/usePagination.ts
61
+ import { ref, computed, watch, type Ref } from 'vue'
62
+
63
+ export function usePagination<T>(
64
+ items: Ref<T[]>,
65
+ options: { pageSize?: number } = {}
66
+ ) {
67
+ const pageSize = options.pageSize ?? 20
68
+ const currentPage = ref(1)
69
+
70
+ const totalPages = computed(() =>
71
+ Math.ceil(items.value.length / pageSize)
72
+ )
73
+
74
+ const paginatedItems = computed(() => {
75
+ const start = (currentPage.value - 1) * pageSize
76
+ return items.value.slice(start, start + pageSize)
77
+ })
78
+
79
+ function goToPage(page: number) {
80
+ currentPage.value = Math.max(1, Math.min(page, totalPages.value))
81
+ }
82
+
83
+ // Reset to page 1 when items change
84
+ watch(items, () => { currentPage.value = 1 })
85
+
86
+ return { currentPage, totalPages, paginatedItems, goToPage }
87
+ }
88
+ ```
89
+
90
+ ```typescript
91
+ // composables/useAsync.ts
92
+ export function useAsync<T>(fn: () => Promise<T>) {
93
+ const data = ref<T | null>(null) as Ref<T | null>
94
+ const error = ref<Error | null>(null)
95
+ const loading = ref(false)
96
+
97
+ async function execute() {
98
+ loading.value = true
99
+ error.value = null
100
+ try {
101
+ data.value = await fn()
102
+ } catch (e) {
103
+ error.value = e instanceof Error ? e : new Error(String(e))
104
+ } finally {
105
+ loading.value = false
106
+ }
107
+ }
108
+
109
+ return { data, error, loading, execute }
110
+ }
111
+ ```
112
+
113
+ ### Pinia Stores
114
+
115
+ Use setup-style stores for full Composition API flexibility. Keep stores focused
116
+ on one domain. Use `storeToRefs` for destructuring without losing reactivity.
117
+
118
+ ```typescript
119
+ // stores/cart.ts
120
+ import { defineStore } from 'pinia'
121
+ import { ref, computed } from 'vue'
122
+
123
+ export const useCartStore = defineStore('cart', () => {
124
+ const items = ref<CartItem[]>([])
125
+
126
+ const total = computed(() =>
127
+ items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
128
+ )
129
+
130
+ const itemCount = computed(() =>
131
+ items.value.reduce((sum, item) => sum + item.quantity, 0)
132
+ )
133
+
134
+ function addItem(product: Product, quantity = 1) {
135
+ const existing = items.value.find(i => i.productId === product.id)
136
+ if (existing) {
137
+ existing.quantity += quantity
138
+ } else {
139
+ items.value.push({ productId: product.id, name: product.name, price: product.price, quantity })
140
+ }
141
+ }
142
+
143
+ function removeItem(productId: string) {
144
+ items.value = items.value.filter(i => i.productId !== productId)
145
+ }
146
+
147
+ function clear() {
148
+ items.value = []
149
+ }
150
+
151
+ return { items, total, itemCount, addItem, removeItem, clear }
152
+ })
153
+ ```
154
+
155
+ ### Slots for Composition
156
+
157
+ Use named slots for layout composition. Use scoped slots to expose data to the
158
+ parent. Default slot content serves as fallback.
159
+
160
+ ```vue
161
+ <!-- BaseCard.vue -->
162
+ <template>
163
+ <div class="card">
164
+ <div v-if="$slots.header" class="card-header">
165
+ <slot name="header" />
166
+ </div>
167
+ <div class="card-body">
168
+ <slot :loading="loading" :error="error" />
169
+ </div>
170
+ <div v-if="$slots.footer" class="card-footer">
171
+ <slot name="footer" />
172
+ </div>
173
+ </div>
174
+ </template>
175
+
176
+ <!-- Usage with scoped slot -->
177
+ <BaseCard>
178
+ <template #header>User Profile</template>
179
+ <template #default="{ loading, error }">
180
+ <Spinner v-if="loading" />
181
+ <ErrorBanner v-else-if="error" :error="error" />
182
+ <UserDetails v-else :user="user" />
183
+ </template>
184
+ </BaseCard>
185
+ ```
186
+
187
+ ### Teleport
188
+
189
+ Use `<Teleport>` to render modals, tooltips, and toasts outside the component's
190
+ DOM hierarchy while keeping them logically owned by the component.
191
+
192
+ ```vue
193
+ <template>
194
+ <button @click="showModal = true">Open</button>
195
+ <Teleport to="body">
196
+ <div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
197
+ <div class="modal-content">
198
+ <slot />
199
+ <button @click="showModal = false">Close</button>
200
+ </div>
201
+ </div>
202
+ </Teleport>
203
+ </template>
204
+ ```
205
+
206
+ ### Transitions
207
+
208
+ Use `<Transition>` for enter/leave animations. Use `<TransitionGroup>` for lists.
209
+ Define CSS classes or use JavaScript hooks for complex animations.
210
+
211
+ ```vue
212
+ <template>
213
+ <Transition name="fade" mode="out-in">
214
+ <component :is="currentView" :key="currentView.__name" />
215
+ </Transition>
216
+ </template>
217
+
218
+ <style>
219
+ .fade-enter-active, .fade-leave-active { transition: opacity 0.2s ease; }
220
+ .fade-enter-from, .fade-leave-to { opacity: 0; }
221
+ </style>
222
+ ```
223
+
224
+ ## Examples
225
+
226
+ **Pattern: Provide/Inject for deep dependency passing**
227
+ ```typescript
228
+ // Parent
229
+ const theme = ref<'light' | 'dark'>('light')
230
+ provide('theme', readonly(theme))
231
+
232
+ // Deep child
233
+ const theme = inject<Ref<string>>('theme', ref('light'))
234
+ ```
235
+
236
+ ## Checklist
237
+
238
+ - [ ] `<script setup lang="ts">` on every component
239
+ - [ ] Props defined with `defineProps<T>()`, emits with `defineEmits<T>()`
240
+ - [ ] Composables named `use*`, return reactive refs and functions
241
+ - [ ] Pinia stores use setup-style syntax; `storeToRefs` for destructuring
242
+ - [ ] Named slots for layout regions; scoped slots for exposing data
243
+ - [ ] `<Teleport to="body">` for modals, tooltips, and overlays
244
+ - [ ] `<Transition>` with `mode="out-in"` for route/view transitions
245
+ - [ ] `v-model` with `defineModel()` (Vue 3.4+) for two-way binding
246
+ - [ ] `watchEffect` for side effects, `watch` when you need old/new values
247
+ - [ ] `provide/inject` with `readonly()` for deep dependency passing