node-mac-recorder 2.21.40 → 2.21.42

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.
@@ -0,0 +1,832 @@
1
+ # Creavit Desktop Integration - Code Snippets
2
+
3
+ ## 📦 Kurulum
4
+
5
+ ```bash
6
+ # creavit.studio/desktop dizininde
7
+ npm install --save /path/to/node-mac-recorder
8
+ ```
9
+
10
+ ## 🔧 Main Process (Electron)
11
+
12
+ ### 1. MultiWindowRecorder Import
13
+
14
+ **Dosya:** `src/main/recording/index.ts`
15
+
16
+ ```typescript
17
+ import MultiWindowRecorder from 'node-mac-recorder/MultiWindowRecorder';
18
+
19
+ // Global recorder instance
20
+ let currentMultiRecorder: MultiWindowRecorder | null = null;
21
+ ```
22
+
23
+ ### 2. IPC Handlers
24
+
25
+ **Dosya:** `src/main/ipc/recording-handlers.ts`
26
+
27
+ ```typescript
28
+ import { ipcMain } from 'electron';
29
+ import MultiWindowRecorder from 'node-mac-recorder/MultiWindowRecorder';
30
+ import path from 'path';
31
+ import { app } from 'electron';
32
+
33
+ let multiRecorder: MultiWindowRecorder | null = null;
34
+
35
+ // Initialize Multi-Window Recorder
36
+ ipcMain.handle('recording:multi-window:init', async () => {
37
+ if (multiRecorder) {
38
+ multiRecorder.destroy();
39
+ }
40
+
41
+ multiRecorder = new MultiWindowRecorder({
42
+ frameRate: 30,
43
+ captureCursor: true,
44
+ preferScreenCaptureKit: true
45
+ });
46
+
47
+ return { success: true };
48
+ });
49
+
50
+ // Add Window
51
+ ipcMain.handle('recording:multi-window:add', async (event, windowInfo) => {
52
+ if (!multiRecorder) {
53
+ throw new Error('Multi-recorder not initialized');
54
+ }
55
+
56
+ const index = await multiRecorder.addWindow(windowInfo);
57
+
58
+ return {
59
+ success: true,
60
+ index,
61
+ windowCount: multiRecorder.getWindowCount()
62
+ };
63
+ });
64
+
65
+ // Remove Window
66
+ ipcMain.handle('recording:multi-window:remove', async (event, index) => {
67
+ if (!multiRecorder) {
68
+ throw new Error('Multi-recorder not initialized');
69
+ }
70
+
71
+ multiRecorder.removeWindow(index);
72
+
73
+ return {
74
+ success: true,
75
+ windowCount: multiRecorder.getWindowCount()
76
+ };
77
+ });
78
+
79
+ // Start Recording
80
+ ipcMain.handle('recording:multi-window:start', async () => {
81
+ if (!multiRecorder) {
82
+ throw new Error('Multi-recorder not initialized');
83
+ }
84
+
85
+ const outputDir = path.join(app.getPath('userData'), 'recordings', `rec_${Date.now()}`);
86
+
87
+ // Create output directory
88
+ const fs = require('fs');
89
+ if (!fs.existsSync(outputDir)) {
90
+ fs.mkdirSync(outputDir, { recursive: true });
91
+ }
92
+
93
+ const result = await multiRecorder.startRecording(outputDir);
94
+
95
+ return {
96
+ success: true,
97
+ ...result
98
+ };
99
+ });
100
+
101
+ // Stop Recording
102
+ ipcMain.handle('recording:multi-window:stop', async () => {
103
+ if (!multiRecorder) {
104
+ throw new Error('Multi-recorder not initialized');
105
+ }
106
+
107
+ const result = await multiRecorder.stopRecording();
108
+
109
+ // Get CRVT metadata
110
+ const crvtMetadata = multiRecorder.getMetadataForCRVT();
111
+
112
+ return {
113
+ success: true,
114
+ ...result,
115
+ crvtMetadata
116
+ };
117
+ });
118
+
119
+ // Get Status
120
+ ipcMain.handle('recording:multi-window:status', async () => {
121
+ if (!multiRecorder) {
122
+ return { isRecording: false, windowCount: 0 };
123
+ }
124
+
125
+ return multiRecorder.getStatus();
126
+ });
127
+
128
+ // Cleanup
129
+ ipcMain.handle('recording:multi-window:destroy', async () => {
130
+ if (multiRecorder) {
131
+ multiRecorder.destroy();
132
+ multiRecorder = null;
133
+ }
134
+
135
+ return { success: true };
136
+ });
137
+ ```
138
+
139
+ ## 🎨 Renderer Process (React)
140
+
141
+ ### 1. Type Definitions
142
+
143
+ **Dosya:** `src/renderer/types/recording.ts`
144
+
145
+ ```typescript
146
+ export interface WindowInfo {
147
+ id: number;
148
+ appName: string;
149
+ title: string;
150
+ width: number;
151
+ height: number;
152
+ x: number;
153
+ y: number;
154
+ }
155
+
156
+ export interface MultiWindowRecordingState {
157
+ windows: WindowInfo[];
158
+ isRecording: boolean;
159
+ outputFiles: string[];
160
+ duration: number;
161
+ }
162
+ ```
163
+
164
+ ### 2. Recording Context/Store
165
+
166
+ **Dosya:** `src/renderer/contexts/RecordingContext.tsx`
167
+
168
+ ```typescript
169
+ import React, { createContext, useContext, useState } from 'react';
170
+
171
+ interface RecordingContextType {
172
+ selectedWindows: WindowInfo[];
173
+ isRecording: boolean;
174
+ addWindow: (window: WindowInfo) => Promise<void>;
175
+ removeWindow: (index: number) => void;
176
+ startRecording: () => Promise<void>;
177
+ stopRecording: () => Promise<void>;
178
+ }
179
+
180
+ const RecordingContext = createContext<RecordingContextType | null>(null);
181
+
182
+ export const RecordingProvider: React.FC = ({ children }) => {
183
+ const [selectedWindows, setSelectedWindows] = useState<WindowInfo[]>([]);
184
+ const [isRecording, setIsRecording] = useState(false);
185
+
186
+ const addWindow = async (window: WindowInfo) => {
187
+ // Initialize if first window
188
+ if (selectedWindows.length === 0) {
189
+ await window.electron.invoke('recording:multi-window:init');
190
+ }
191
+
192
+ // Add window
193
+ const result = await window.electron.invoke('recording:multi-window:add', window);
194
+
195
+ if (result.success) {
196
+ setSelectedWindows([...selectedWindows, window]);
197
+ }
198
+ };
199
+
200
+ const removeWindow = async (index: number) => {
201
+ await window.electron.invoke('recording:multi-window:remove', index);
202
+ setSelectedWindows(selectedWindows.filter((_, i) => i !== index));
203
+ };
204
+
205
+ const startRecording = async () => {
206
+ const result = await window.electron.invoke('recording:multi-window:start');
207
+
208
+ if (result.success) {
209
+ setIsRecording(true);
210
+ }
211
+ };
212
+
213
+ const stopRecording = async () => {
214
+ const result = await window.electron.invoke('recording:multi-window:stop');
215
+
216
+ if (result.success) {
217
+ setIsRecording(false);
218
+
219
+ // Create CRVT file
220
+ await createCRVTFile(result);
221
+
222
+ // Open editor
223
+ await openEditor(result.outputFiles[0]);
224
+ }
225
+ };
226
+
227
+ return (
228
+ <RecordingContext.Provider
229
+ value={{
230
+ selectedWindows,
231
+ isRecording,
232
+ addWindow,
233
+ removeWindow,
234
+ startRecording,
235
+ stopRecording
236
+ }}
237
+ >
238
+ {children}
239
+ </RecordingContext.Provider>
240
+ );
241
+ };
242
+
243
+ export const useRecording = () => {
244
+ const context = useContext(RecordingContext);
245
+ if (!context) {
246
+ throw new Error('useRecording must be used within RecordingProvider');
247
+ }
248
+ return context;
249
+ };
250
+ ```
251
+
252
+ ### 3. Multi-Window Selector Component
253
+
254
+ **Dosya:** `src/renderer/components/recording/MultiWindowSelector.tsx`
255
+
256
+ ```typescript
257
+ import React from 'react';
258
+ import { useRecording } from '../../contexts/RecordingContext';
259
+ import { WindowPreviewCard } from './WindowPreviewCard';
260
+
261
+ export const MultiWindowSelector: React.FC = () => {
262
+ const { selectedWindows, addWindow, removeWindow, isRecording } = useRecording();
263
+
264
+ const handleSelectWindow = async (index: number) => {
265
+ // Show window picker overlay
266
+ const pickedWindow = await window.electron.invoke('window-picker:show');
267
+
268
+ if (pickedWindow) {
269
+ if (index < selectedWindows.length) {
270
+ // Replace existing
271
+ removeWindow(index);
272
+ }
273
+ await addWindow(pickedWindow);
274
+ }
275
+ };
276
+
277
+ return (
278
+ <div className="multi-window-selector">
279
+ <div className="selector-header">
280
+ <h3>Kayıt Edilecek Pencereler</h3>
281
+ <span className="window-count">{selectedWindows.length} pencere</span>
282
+ </div>
283
+
284
+ <div className="windows-grid">
285
+ {/* Window Slot 1 */}
286
+ <div className="window-slot">
287
+ {selectedWindows[0] ? (
288
+ <WindowPreviewCard
289
+ window={selectedWindows[0]}
290
+ onReselect={() => handleSelectWindow(0)}
291
+ onRemove={() => removeWindow(0)}
292
+ disabled={isRecording}
293
+ />
294
+ ) : (
295
+ <button
296
+ className="select-window-btn"
297
+ onClick={() => handleSelectWindow(0)}
298
+ disabled={isRecording}
299
+ >
300
+ <VideoIcon />
301
+ <span>Pencere Seç</span>
302
+ </button>
303
+ )}
304
+ </div>
305
+
306
+ {/* Window Slot 2 - Show only if first window is selected */}
307
+ {selectedWindows[0] && (
308
+ <div className="window-slot">
309
+ {selectedWindows[1] ? (
310
+ <WindowPreviewCard
311
+ window={selectedWindows[1]}
312
+ onReselect={() => handleSelectWindow(1)}
313
+ onRemove={() => removeWindow(1)}
314
+ disabled={isRecording}
315
+ />
316
+ ) : (
317
+ <button
318
+ className="select-window-btn add-second"
319
+ onClick={() => handleSelectWindow(1)}
320
+ disabled={isRecording}
321
+ >
322
+ <PlusIcon />
323
+ <span>İkinci Pencere Ekle</span>
324
+ </button>
325
+ )}
326
+ </div>
327
+ )}
328
+ </div>
329
+ </div>
330
+ );
331
+ };
332
+ ```
333
+
334
+ ### 4. Window Preview Card
335
+
336
+ **Dosya:** `src/renderer/components/recording/WindowPreviewCard.tsx`
337
+
338
+ ```typescript
339
+ import React from 'react';
340
+ import { WindowInfo } from '../../types/recording';
341
+
342
+ interface Props {
343
+ window: WindowInfo;
344
+ onReselect: () => void;
345
+ onRemove: () => void;
346
+ disabled?: boolean;
347
+ }
348
+
349
+ export const WindowPreviewCard: React.FC<Props> = ({
350
+ window,
351
+ onReselect,
352
+ onRemove,
353
+ disabled
354
+ }) => {
355
+ return (
356
+ <div className="window-preview-card">
357
+ <div className="preview-header">
358
+ <span className="app-name">{window.appName}</span>
359
+ <button
360
+ className="remove-btn"
361
+ onClick={onRemove}
362
+ disabled={disabled}
363
+ >
364
+ ×
365
+ </button>
366
+ </div>
367
+
368
+ <div className="preview-content">
369
+ {/* Thumbnail buraya gelecek */}
370
+ <div className="window-icon">
371
+ <VideoIcon />
372
+ </div>
373
+ <div className="window-info">
374
+ <div className="window-title">{window.title || 'Başlıksız'}</div>
375
+ <div className="window-size">{window.width} × {window.height}</div>
376
+ </div>
377
+ </div>
378
+
379
+ <button
380
+ className="reselect-btn"
381
+ onClick={onReselect}
382
+ disabled={disabled}
383
+ >
384
+ Değiştir
385
+ </button>
386
+ </div>
387
+ );
388
+ };
389
+ ```
390
+
391
+ ### 5. Recording Controls
392
+
393
+ **Dosya:** `src/renderer/components/recording/RecordingControls.tsx`
394
+
395
+ ```typescript
396
+ import React from 'react';
397
+ import { useRecording } from '../../contexts/RecordingContext';
398
+
399
+ export const RecordingControls: React.FC = () => {
400
+ const { selectedWindows, isRecording, startRecording, stopRecording } = useRecording();
401
+
402
+ const canStart = selectedWindows.length > 0 && !isRecording;
403
+
404
+ return (
405
+ <div className="recording-controls">
406
+ {!isRecording ? (
407
+ <button
408
+ className="start-recording-btn"
409
+ onClick={startRecording}
410
+ disabled={!canStart}
411
+ >
412
+ <RecordIcon />
413
+ <span>Kayıt Başlat</span>
414
+ {selectedWindows.length > 1 && (
415
+ <span className="window-badge">{selectedWindows.length} pencere</span>
416
+ )}
417
+ </button>
418
+ ) : (
419
+ <button
420
+ className="stop-recording-btn"
421
+ onClick={stopRecording}
422
+ >
423
+ <StopIcon />
424
+ <span>Kaydı Durdur</span>
425
+ </button>
426
+ )}
427
+ </div>
428
+ );
429
+ };
430
+ ```
431
+
432
+ ## 📄 CRVT File Creation
433
+
434
+ **Dosya:** `src/main/utils/crvt-creator.ts`
435
+
436
+ ```typescript
437
+ import fs from 'fs';
438
+ import path from 'path';
439
+
440
+ interface CRVTSegment {
441
+ id: string;
442
+ type: 'screen' | 'cursor' | 'audio' | 'camera';
443
+ filePath: string;
444
+ startTime: number;
445
+ endTime: number;
446
+ duration: number;
447
+ windowIndex?: number;
448
+ layoutRow?: number;
449
+ }
450
+
451
+ interface CRVTFile {
452
+ version: string;
453
+ timestamp: number;
454
+ duration: number;
455
+ segments: CRVTSegment[];
456
+ multiWindow?: {
457
+ enabled: boolean;
458
+ windowCount: number;
459
+ windows: Array<{
460
+ index: number;
461
+ appName: string;
462
+ title: string;
463
+ filePath: string;
464
+ syncOffset: number;
465
+ }>;
466
+ };
467
+ }
468
+
469
+ export async function createMultiWindowCRVT(
470
+ recordingResult: any,
471
+ outputDir: string
472
+ ): Promise<string> {
473
+ const crvt: CRVTFile = {
474
+ version: '2.0',
475
+ timestamp: recordingResult.metadata.startTime,
476
+ duration: recordingResult.duration,
477
+ segments: [],
478
+ multiWindow: {
479
+ enabled: true,
480
+ windowCount: recordingResult.windowCount,
481
+ windows: []
482
+ }
483
+ };
484
+
485
+ // Create segments for each window
486
+ recordingResult.metadata.windows.forEach((win: any, index: number) => {
487
+ // Screen segment
488
+ crvt.segments.push({
489
+ id: `screen_${index}_${Date.now()}`,
490
+ type: 'screen',
491
+ filePath: win.outputPath,
492
+ startTime: 0,
493
+ endTime: recordingResult.duration,
494
+ duration: recordingResult.duration,
495
+ windowIndex: index,
496
+ layoutRow: index
497
+ });
498
+
499
+ // Cursor segment (if exists)
500
+ const cursorFile = findCursorFile(win.outputPath);
501
+ if (cursorFile && fs.existsSync(cursorFile)) {
502
+ crvt.segments.push({
503
+ id: `cursor_${index}_${Date.now()}`,
504
+ type: 'cursor',
505
+ filePath: cursorFile,
506
+ startTime: 0,
507
+ endTime: recordingResult.duration,
508
+ duration: recordingResult.duration,
509
+ windowIndex: index,
510
+ layoutRow: index
511
+ });
512
+ }
513
+
514
+ // Add window metadata
515
+ crvt.multiWindow!.windows.push({
516
+ index,
517
+ appName: win.windowInfo.appName,
518
+ title: win.windowInfo.title,
519
+ filePath: win.outputPath,
520
+ syncOffset: win.syncOffset
521
+ });
522
+ });
523
+
524
+ // Save CRVT file
525
+ const crvtPath = path.join(outputDir, 'recording.crvt');
526
+ fs.writeFileSync(crvtPath, JSON.stringify(crvt, null, 2));
527
+
528
+ console.log(`📄 CRVT file created: ${crvtPath}`);
529
+
530
+ return crvtPath;
531
+ }
532
+
533
+ function findCursorFile(videoPath: string): string | null {
534
+ const dir = path.dirname(videoPath);
535
+ const basename = path.basename(videoPath, path.extname(videoPath));
536
+
537
+ // Try to find cursor file
538
+ const cursorPath = path.join(dir, `temp_cursor_${basename.split('_').pop()}.json`);
539
+
540
+ return fs.existsSync(cursorPath) ? cursorPath : null;
541
+ }
542
+ ```
543
+
544
+ ## 🎬 Editor Integration
545
+
546
+ **Dosya:** `src/renderer/components/editor/MultiRowTimeline.tsx`
547
+
548
+ ```typescript
549
+ import React, { useMemo } from 'react';
550
+ import { CRVTFile, CRVTSegment } from '../../types/crvt';
551
+ import { ClipSegment } from './ClipSegment';
552
+
553
+ interface Props {
554
+ recording: CRVTFile;
555
+ onSegmentSelect?: (segment: CRVTSegment) => void;
556
+ }
557
+
558
+ export const MultiRowTimeline: React.FC<Props> = ({
559
+ recording,
560
+ onSegmentSelect
561
+ }) => {
562
+ // Group segments by layout row
563
+ const segmentsByRow = useMemo(() => {
564
+ const rows = new Map<number, CRVTSegment[]>();
565
+
566
+ recording.segments.forEach(segment => {
567
+ const row = segment.layoutRow ?? 0;
568
+ if (!rows.has(row)) {
569
+ rows.set(row, []);
570
+ }
571
+ rows.get(row)!.push(segment);
572
+ });
573
+
574
+ return rows;
575
+ }, [recording]);
576
+
577
+ return (
578
+ <div className="multi-row-timeline">
579
+ {Array.from(segmentsByRow.entries()).map(([rowIndex, segments]) => (
580
+ <div
581
+ key={rowIndex}
582
+ className="timeline-row"
583
+ data-row={rowIndex}
584
+ >
585
+ <div className="row-label">
586
+ <div className="row-number">Row {rowIndex + 1}</div>
587
+ <div className="row-app-name">
588
+ {recording.multiWindow?.windows[rowIndex]?.appName || `Window ${rowIndex + 1}`}
589
+ </div>
590
+ </div>
591
+
592
+ <div className="row-track">
593
+ {segments.map(segment => (
594
+ <ClipSegment
595
+ key={segment.id}
596
+ segment={segment}
597
+ totalDuration={recording.duration}
598
+ onSelect={() => onSegmentSelect?.(segment)}
599
+ />
600
+ ))}
601
+ </div>
602
+ </div>
603
+ ))}
604
+ </div>
605
+ );
606
+ };
607
+ ```
608
+
609
+ ## 🎨 CSS Styles
610
+
611
+ **Dosya:** `src/renderer/styles/multi-window.css`
612
+
613
+ ```css
614
+ /* Multi-Window Selector */
615
+ .multi-window-selector {
616
+ padding: 20px;
617
+ }
618
+
619
+ .selector-header {
620
+ display: flex;
621
+ justify-content: space-between;
622
+ align-items: center;
623
+ margin-bottom: 20px;
624
+ }
625
+
626
+ .window-count {
627
+ font-size: 14px;
628
+ color: #888;
629
+ }
630
+
631
+ .windows-grid {
632
+ display: grid;
633
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
634
+ gap: 20px;
635
+ }
636
+
637
+ .window-slot {
638
+ min-height: 200px;
639
+ }
640
+
641
+ .select-window-btn {
642
+ width: 100%;
643
+ height: 200px;
644
+ border: 2px dashed #444;
645
+ border-radius: 8px;
646
+ background: transparent;
647
+ color: #fff;
648
+ cursor: pointer;
649
+ display: flex;
650
+ flex-direction: column;
651
+ align-items: center;
652
+ justify-content: center;
653
+ gap: 12px;
654
+ transition: all 0.2s;
655
+ }
656
+
657
+ .select-window-btn:hover {
658
+ border-color: #666;
659
+ background: rgba(255, 255, 255, 0.05);
660
+ }
661
+
662
+ .select-window-btn.add-second {
663
+ border-color: #0066ff;
664
+ }
665
+
666
+ .select-window-btn.add-second:hover {
667
+ background: rgba(0, 102, 255, 0.1);
668
+ }
669
+
670
+ /* Window Preview Card */
671
+ .window-preview-card {
672
+ border: 1px solid #333;
673
+ border-radius: 8px;
674
+ background: #1a1a1a;
675
+ overflow: hidden;
676
+ }
677
+
678
+ .preview-header {
679
+ display: flex;
680
+ justify-content: space-between;
681
+ align-items: center;
682
+ padding: 12px;
683
+ border-bottom: 1px solid #333;
684
+ }
685
+
686
+ .app-name {
687
+ font-weight: 600;
688
+ color: #fff;
689
+ }
690
+
691
+ .remove-btn {
692
+ width: 24px;
693
+ height: 24px;
694
+ border: none;
695
+ background: #ff4444;
696
+ color: #fff;
697
+ border-radius: 4px;
698
+ cursor: pointer;
699
+ font-size: 18px;
700
+ line-height: 1;
701
+ }
702
+
703
+ .preview-content {
704
+ padding: 20px;
705
+ text-align: center;
706
+ }
707
+
708
+ .window-info {
709
+ margin-top: 12px;
710
+ }
711
+
712
+ .window-title {
713
+ font-size: 14px;
714
+ color: #ccc;
715
+ }
716
+
717
+ .window-size {
718
+ font-size: 12px;
719
+ color: #888;
720
+ margin-top: 4px;
721
+ }
722
+
723
+ .reselect-btn {
724
+ width: 100%;
725
+ padding: 10px;
726
+ border: none;
727
+ border-top: 1px solid #333;
728
+ background: transparent;
729
+ color: #0066ff;
730
+ cursor: pointer;
731
+ }
732
+
733
+ /* Recording Controls */
734
+ .start-recording-btn {
735
+ display: flex;
736
+ align-items: center;
737
+ gap: 12px;
738
+ padding: 16px 32px;
739
+ background: #ff0000;
740
+ color: #fff;
741
+ border: none;
742
+ border-radius: 8px;
743
+ font-size: 16px;
744
+ font-weight: 600;
745
+ cursor: pointer;
746
+ }
747
+
748
+ .window-badge {
749
+ padding: 4px 8px;
750
+ background: rgba(255, 255, 255, 0.2);
751
+ border-radius: 12px;
752
+ font-size: 12px;
753
+ }
754
+
755
+ /* Multi-Row Timeline */
756
+ .multi-row-timeline {
757
+ display: flex;
758
+ flex-direction: column;
759
+ gap: 8px;
760
+ padding: 20px;
761
+ }
762
+
763
+ .timeline-row {
764
+ display: flex;
765
+ min-height: 80px;
766
+ border: 1px solid #333;
767
+ border-radius: 4px;
768
+ background: #1a1a1a;
769
+ }
770
+
771
+ .row-label {
772
+ width: 150px;
773
+ padding: 12px;
774
+ border-right: 1px solid #333;
775
+ display: flex;
776
+ flex-direction: column;
777
+ gap: 4px;
778
+ }
779
+
780
+ .row-number {
781
+ font-size: 12px;
782
+ color: #888;
783
+ }
784
+
785
+ .row-app-name {
786
+ font-weight: 600;
787
+ color: #fff;
788
+ }
789
+
790
+ .row-track {
791
+ flex: 1;
792
+ position: relative;
793
+ padding: 8px;
794
+ }
795
+ ```
796
+
797
+ ## 🚀 Kullanım Örneği
798
+
799
+ ```typescript
800
+ // RecordingWindow.tsx
801
+ import React from 'react';
802
+ import { RecordingProvider } from './contexts/RecordingContext';
803
+ import { MultiWindowSelector } from './components/recording/MultiWindowSelector';
804
+ import { RecordingControls } from './components/recording/RecordingControls';
805
+
806
+ export const RecordingWindow: React.FC = () => {
807
+ return (
808
+ <RecordingProvider>
809
+ <div className="recording-window">
810
+ <h1>Yeni Kayıt</h1>
811
+
812
+ <MultiWindowSelector />
813
+
814
+ <RecordingControls />
815
+ </div>
816
+ </RecordingProvider>
817
+ );
818
+ };
819
+ ```
820
+
821
+ ## ✅ Checklist
822
+
823
+ - [ ] MultiWindowRecorder import et
824
+ - [ ] IPC handlers ekle
825
+ - [ ] RecordingContext oluştur
826
+ - [ ] MultiWindowSelector komponenti
827
+ - [ ] WindowPreviewCard komponenti
828
+ - [ ] RecordingControls güncelle
829
+ - [ ] CRVT creator implement et
830
+ - [ ] MultiRowTimeline komponenti
831
+ - [ ] CSS stilleri ekle
832
+ - [ ] End-to-end test