markupr 2.1.8

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 (299) hide show
  1. package/.claude/commands/review-feedback.md +47 -0
  2. package/.eslintrc.json +35 -0
  3. package/.github/CODEOWNERS +16 -0
  4. package/.github/FUNDING.yml +1 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +56 -0
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +54 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +89 -0
  8. package/.github/dependabot.yml +70 -0
  9. package/.github/workflows/ci.yml +184 -0
  10. package/.github/workflows/deploy-landing.yml +134 -0
  11. package/.github/workflows/nightly.yml +288 -0
  12. package/.github/workflows/release.yml +318 -0
  13. package/CHANGELOG.md +127 -0
  14. package/CLAUDE.md +137 -0
  15. package/CODE_OF_CONDUCT.md +9 -0
  16. package/CONTRIBUTING.md +390 -0
  17. package/LICENSE +21 -0
  18. package/PRODUCT_VISION.md +277 -0
  19. package/README.md +517 -0
  20. package/SECURITY.md +51 -0
  21. package/SIGNING_INSTRUCTIONS.md +284 -0
  22. package/assets/DMG_BACKGROUND_INSTRUCTIONS.md +130 -0
  23. package/assets/svg-source/dmg-background.svg +70 -0
  24. package/assets/svg-source/icon.svg +20 -0
  25. package/assets/svg-source/tray-icon-processing.svg +7 -0
  26. package/assets/svg-source/tray-icon-recording.svg +7 -0
  27. package/assets/svg-source/tray-icon.svg +6 -0
  28. package/assets/tray-complete.png +0 -0
  29. package/assets/tray-complete@2x.png +0 -0
  30. package/assets/tray-completeTemplate.png +0 -0
  31. package/assets/tray-completeTemplate@2x.png +0 -0
  32. package/assets/tray-error.png +0 -0
  33. package/assets/tray-error@2x.png +0 -0
  34. package/assets/tray-errorTemplate.png +0 -0
  35. package/assets/tray-errorTemplate@2x.png +0 -0
  36. package/assets/tray-icon-processing.png +0 -0
  37. package/assets/tray-icon-processing@2x.png +0 -0
  38. package/assets/tray-icon-processingTemplate.png +0 -0
  39. package/assets/tray-icon-processingTemplate@2x.png +0 -0
  40. package/assets/tray-icon-recording.png +0 -0
  41. package/assets/tray-icon-recording@2x.png +0 -0
  42. package/assets/tray-icon-recordingTemplate.png +0 -0
  43. package/assets/tray-icon-recordingTemplate@2x.png +0 -0
  44. package/assets/tray-icon.png +0 -0
  45. package/assets/tray-icon@2x.png +0 -0
  46. package/assets/tray-iconTemplate.png +0 -0
  47. package/assets/tray-iconTemplate@2x.png +0 -0
  48. package/assets/tray-idle.png +0 -0
  49. package/assets/tray-idle@2x.png +0 -0
  50. package/assets/tray-idleTemplate.png +0 -0
  51. package/assets/tray-idleTemplate@2x.png +0 -0
  52. package/assets/tray-processing-0.png +0 -0
  53. package/assets/tray-processing-0@2x.png +0 -0
  54. package/assets/tray-processing-0Template.png +0 -0
  55. package/assets/tray-processing-0Template@2x.png +0 -0
  56. package/assets/tray-processing-1.png +0 -0
  57. package/assets/tray-processing-1@2x.png +0 -0
  58. package/assets/tray-processing-1Template.png +0 -0
  59. package/assets/tray-processing-1Template@2x.png +0 -0
  60. package/assets/tray-processing-2.png +0 -0
  61. package/assets/tray-processing-2@2x.png +0 -0
  62. package/assets/tray-processing-2Template.png +0 -0
  63. package/assets/tray-processing-2Template@2x.png +0 -0
  64. package/assets/tray-processing-3.png +0 -0
  65. package/assets/tray-processing-3@2x.png +0 -0
  66. package/assets/tray-processing-3Template.png +0 -0
  67. package/assets/tray-processing-3Template@2x.png +0 -0
  68. package/assets/tray-processing.png +0 -0
  69. package/assets/tray-processing@2x.png +0 -0
  70. package/assets/tray-processingTemplate.png +0 -0
  71. package/assets/tray-processingTemplate@2x.png +0 -0
  72. package/assets/tray-recording.png +0 -0
  73. package/assets/tray-recording@2x.png +0 -0
  74. package/assets/tray-recordingTemplate.png +0 -0
  75. package/assets/tray-recordingTemplate@2x.png +0 -0
  76. package/build/DMG_BACKGROUND_SPEC.md +50 -0
  77. package/build/dmg-background.png +0 -0
  78. package/build/dmg-background@2x.png +0 -0
  79. package/build/entitlements.mac.inherit.plist +27 -0
  80. package/build/entitlements.mac.plist +41 -0
  81. package/build/favicon-16.png +0 -0
  82. package/build/favicon-180.png +0 -0
  83. package/build/favicon-192.png +0 -0
  84. package/build/favicon-32.png +0 -0
  85. package/build/favicon-48.png +0 -0
  86. package/build/favicon-512.png +0 -0
  87. package/build/favicon-64.png +0 -0
  88. package/build/icon-128.png +0 -0
  89. package/build/icon-16.png +0 -0
  90. package/build/icon-24.png +0 -0
  91. package/build/icon-256.png +0 -0
  92. package/build/icon-32.png +0 -0
  93. package/build/icon-48.png +0 -0
  94. package/build/icon-64.png +0 -0
  95. package/build/icon.icns +0 -0
  96. package/build/icon.ico +0 -0
  97. package/build/icon.iconset/icon_128x128.png +0 -0
  98. package/build/icon.iconset/icon_128x128@2x.png +0 -0
  99. package/build/icon.iconset/icon_16x16.png +0 -0
  100. package/build/icon.iconset/icon_16x16@2x.png +0 -0
  101. package/build/icon.iconset/icon_256x256.png +0 -0
  102. package/build/icon.iconset/icon_256x256@2x.png +0 -0
  103. package/build/icon.iconset/icon_32x32.png +0 -0
  104. package/build/icon.iconset/icon_32x32@2x.png +0 -0
  105. package/build/icon.iconset/icon_512x512.png +0 -0
  106. package/build/icon.iconset/icon_512x512@2x.png +0 -0
  107. package/build/icon.png +0 -0
  108. package/build/installer-header.bmp +0 -0
  109. package/build/installer-header.png +0 -0
  110. package/build/installer-sidebar.bmp +0 -0
  111. package/build/installer-sidebar.png +0 -0
  112. package/build/installer.nsh +45 -0
  113. package/build/overlay-processing.png +0 -0
  114. package/build/overlay-recording.png +0 -0
  115. package/build/toolbar-record.png +0 -0
  116. package/build/toolbar-screenshot.png +0 -0
  117. package/build/toolbar-settings.png +0 -0
  118. package/build/toolbar-stop.png +0 -0
  119. package/dist/main/index.mjs +12612 -0
  120. package/dist/preload/index.mjs +907 -0
  121. package/dist/renderer/assets/index-CCmUjl9K.js +19495 -0
  122. package/dist/renderer/assets/index-CUqz_Gs6.css +2270 -0
  123. package/dist/renderer/index.html +27 -0
  124. package/docs/AI_AGENT_QUICKSTART.md +42 -0
  125. package/docs/AI_PIPELINE_DESIGN.md +595 -0
  126. package/docs/API.md +514 -0
  127. package/docs/ARCHITECTURE.md +460 -0
  128. package/docs/CONFIGURATION.md +336 -0
  129. package/docs/DEVELOPMENT.md +508 -0
  130. package/docs/EXPORT_FORMATS.md +451 -0
  131. package/docs/GETTING_STARTED.md +236 -0
  132. package/docs/KEYBOARD_SHORTCUTS.md +334 -0
  133. package/docs/TROUBLESHOOTING.md +418 -0
  134. package/docs/landing/index.html +672 -0
  135. package/docs/landing/script.js +342 -0
  136. package/docs/landing/styles.css +1543 -0
  137. package/electron-builder.yml +140 -0
  138. package/electron.vite.config.ts +63 -0
  139. package/package.json +108 -0
  140. package/railway.json +12 -0
  141. package/scripts/build.mjs +51 -0
  142. package/scripts/generate-icons.mjs +314 -0
  143. package/scripts/generate-installer-images.cjs +253 -0
  144. package/scripts/generate-tray-icons.mjs +258 -0
  145. package/scripts/notarize.cjs +180 -0
  146. package/scripts/one-click-clean-test.sh +147 -0
  147. package/scripts/postinstall.mjs +36 -0
  148. package/scripts/setup-markupr.sh +55 -0
  149. package/setup +17 -0
  150. package/site/index.html +1835 -0
  151. package/site/package.json +11 -0
  152. package/site/railway.json +12 -0
  153. package/site/server.js +31 -0
  154. package/src/main/AutoUpdater.ts +392 -0
  155. package/src/main/CrashRecovery.ts +655 -0
  156. package/src/main/ErrorHandler.ts +703 -0
  157. package/src/main/HotkeyManager.ts +399 -0
  158. package/src/main/MenuManager.ts +529 -0
  159. package/src/main/PermissionManager.ts +420 -0
  160. package/src/main/SessionController.ts +1465 -0
  161. package/src/main/TrayManager.ts +540 -0
  162. package/src/main/ai/AIPipelineManager.ts +199 -0
  163. package/src/main/ai/ClaudeAnalyzer.ts +339 -0
  164. package/src/main/ai/ImageOptimizer.ts +176 -0
  165. package/src/main/ai/StructuredMarkdownBuilder.ts +379 -0
  166. package/src/main/ai/index.ts +16 -0
  167. package/src/main/ai/types.ts +258 -0
  168. package/src/main/analysis/ClarificationGenerator.ts +385 -0
  169. package/src/main/analysis/FeedbackAnalyzer.ts +531 -0
  170. package/src/main/analysis/index.ts +19 -0
  171. package/src/main/audio/AudioCapture.ts +978 -0
  172. package/src/main/audio/audioUtils.ts +100 -0
  173. package/src/main/audio/index.ts +20 -0
  174. package/src/main/capture/index.ts +1 -0
  175. package/src/main/index.ts +1693 -0
  176. package/src/main/ipc/captureHandlers.ts +272 -0
  177. package/src/main/ipc/index.ts +45 -0
  178. package/src/main/ipc/outputHandlers.ts +302 -0
  179. package/src/main/ipc/sessionHandlers.ts +56 -0
  180. package/src/main/ipc/settingsHandlers.ts +471 -0
  181. package/src/main/ipc/types.ts +56 -0
  182. package/src/main/ipc/windowHandlers.ts +277 -0
  183. package/src/main/output/ClipboardService.ts +369 -0
  184. package/src/main/output/ExportService.ts +539 -0
  185. package/src/main/output/FileManager.ts +416 -0
  186. package/src/main/output/MarkdownGenerator.ts +791 -0
  187. package/src/main/output/MarkdownPatcher.ts +299 -0
  188. package/src/main/output/index.ts +186 -0
  189. package/src/main/output/sessionAdapter.ts +207 -0
  190. package/src/main/output/templates/html-template.ts +553 -0
  191. package/src/main/pipeline/FrameExtractor.ts +330 -0
  192. package/src/main/pipeline/PostProcessor.ts +399 -0
  193. package/src/main/pipeline/TranscriptAnalyzer.ts +226 -0
  194. package/src/main/pipeline/index.ts +36 -0
  195. package/src/main/platform/WindowsTaskbar.ts +600 -0
  196. package/src/main/platform/index.ts +16 -0
  197. package/src/main/settings/SettingsManager.ts +730 -0
  198. package/src/main/settings/index.ts +19 -0
  199. package/src/main/transcription/ModelDownloadManager.ts +494 -0
  200. package/src/main/transcription/TierManager.ts +219 -0
  201. package/src/main/transcription/TranscriptionRecoveryService.ts +340 -0
  202. package/src/main/transcription/WhisperService.ts +748 -0
  203. package/src/main/transcription/index.ts +56 -0
  204. package/src/main/transcription/types.ts +135 -0
  205. package/src/main/windows/PopoverManager.ts +284 -0
  206. package/src/main/windows/TaskbarIntegration.ts +452 -0
  207. package/src/main/windows/index.ts +23 -0
  208. package/src/preload/index.ts +1047 -0
  209. package/src/renderer/App.tsx +515 -0
  210. package/src/renderer/AppWrapper.tsx +28 -0
  211. package/src/renderer/assets/logo-dark.svg +7 -0
  212. package/src/renderer/assets/logo.svg +7 -0
  213. package/src/renderer/audio/AudioCaptureRenderer.ts +454 -0
  214. package/src/renderer/capture/ScreenRecordingRenderer.ts +492 -0
  215. package/src/renderer/components/AnnotationOverlay.tsx +836 -0
  216. package/src/renderer/components/AudioWaveform.tsx +811 -0
  217. package/src/renderer/components/ClarificationQuestions.tsx +656 -0
  218. package/src/renderer/components/CountdownTimer.tsx +495 -0
  219. package/src/renderer/components/CrashRecoveryDialog.tsx +632 -0
  220. package/src/renderer/components/DonateButton.tsx +127 -0
  221. package/src/renderer/components/ErrorBoundary.tsx +308 -0
  222. package/src/renderer/components/ExportDialog.tsx +872 -0
  223. package/src/renderer/components/HotkeyHint.tsx +261 -0
  224. package/src/renderer/components/KeyboardShortcuts.tsx +787 -0
  225. package/src/renderer/components/ModelDownloadDialog.tsx +844 -0
  226. package/src/renderer/components/Onboarding.tsx +1830 -0
  227. package/src/renderer/components/ProcessingOverlay.tsx +157 -0
  228. package/src/renderer/components/RecordingOverlay.tsx +423 -0
  229. package/src/renderer/components/SessionHistory.tsx +1746 -0
  230. package/src/renderer/components/SessionReview.tsx +1321 -0
  231. package/src/renderer/components/SettingsPanel.tsx +217 -0
  232. package/src/renderer/components/Skeleton.tsx +347 -0
  233. package/src/renderer/components/StatusIndicator.tsx +86 -0
  234. package/src/renderer/components/ThemeProvider.tsx +429 -0
  235. package/src/renderer/components/Tooltip.tsx +370 -0
  236. package/src/renderer/components/TranscriptionPreview.tsx +183 -0
  237. package/src/renderer/components/TranscriptionTierSelector.tsx +640 -0
  238. package/src/renderer/components/UpdateNotification.tsx +377 -0
  239. package/src/renderer/components/WindowSelector.tsx +947 -0
  240. package/src/renderer/components/index.ts +99 -0
  241. package/src/renderer/components/primitives/ApiKeyInput.tsx +98 -0
  242. package/src/renderer/components/primitives/ColorPicker.tsx +65 -0
  243. package/src/renderer/components/primitives/DangerButton.tsx +45 -0
  244. package/src/renderer/components/primitives/DirectoryPicker.tsx +41 -0
  245. package/src/renderer/components/primitives/Dropdown.tsx +34 -0
  246. package/src/renderer/components/primitives/KeyRecorder.tsx +117 -0
  247. package/src/renderer/components/primitives/SettingsSection.tsx +32 -0
  248. package/src/renderer/components/primitives/Slider.tsx +43 -0
  249. package/src/renderer/components/primitives/Toggle.tsx +36 -0
  250. package/src/renderer/components/primitives/index.ts +10 -0
  251. package/src/renderer/components/settings/AdvancedTab.tsx +174 -0
  252. package/src/renderer/components/settings/AppearanceTab.tsx +77 -0
  253. package/src/renderer/components/settings/GeneralTab.tsx +40 -0
  254. package/src/renderer/components/settings/HotkeysTab.tsx +79 -0
  255. package/src/renderer/components/settings/RecordingTab.tsx +84 -0
  256. package/src/renderer/components/settings/index.ts +9 -0
  257. package/src/renderer/components/settings/settingsStyles.ts +673 -0
  258. package/src/renderer/components/settings/tabConfig.tsx +85 -0
  259. package/src/renderer/components/settings/useSettingsPanel.ts +447 -0
  260. package/src/renderer/contexts/ProcessingContext.tsx +227 -0
  261. package/src/renderer/contexts/RecordingContext.tsx +683 -0
  262. package/src/renderer/contexts/UIContext.tsx +326 -0
  263. package/src/renderer/contexts/index.ts +24 -0
  264. package/src/renderer/donateMessages.ts +69 -0
  265. package/src/renderer/hooks/index.ts +75 -0
  266. package/src/renderer/hooks/useAnimation.tsx +544 -0
  267. package/src/renderer/hooks/useTheme.ts +313 -0
  268. package/src/renderer/index.html +26 -0
  269. package/src/renderer/main.tsx +52 -0
  270. package/src/renderer/styles/animations.css +1093 -0
  271. package/src/renderer/styles/app-shell.css +662 -0
  272. package/src/renderer/styles/globals.css +515 -0
  273. package/src/renderer/styles/theme.ts +578 -0
  274. package/src/renderer/types/electron.d.ts +385 -0
  275. package/src/shared/hotkeys.ts +283 -0
  276. package/src/shared/types.ts +809 -0
  277. package/tests/clipboard.test.ts +228 -0
  278. package/tests/e2e/criticalPaths.test.ts +594 -0
  279. package/tests/feedbackAnalyzer.test.ts +303 -0
  280. package/tests/integration/sessionFlow.test.ts +583 -0
  281. package/tests/markdownGenerator.test.ts +418 -0
  282. package/tests/output.test.ts +96 -0
  283. package/tests/setup.ts +486 -0
  284. package/tests/unit/appIntegration.test.ts +676 -0
  285. package/tests/unit/appViewState.test.ts +281 -0
  286. package/tests/unit/audioIpcChannels.test.ts +17 -0
  287. package/tests/unit/exportService.test.ts +492 -0
  288. package/tests/unit/hotkeys.test.ts +92 -0
  289. package/tests/unit/navigationPreload.test.ts +94 -0
  290. package/tests/unit/onboardingFlow.test.ts +345 -0
  291. package/tests/unit/permissionManager.test.ts +175 -0
  292. package/tests/unit/permissionManagerExpanded.test.ts +296 -0
  293. package/tests/unit/screenRecordingRenderer.test.ts +368 -0
  294. package/tests/unit/sessionController.test.ts +515 -0
  295. package/tests/unit/tierManager.test.ts +61 -0
  296. package/tests/unit/tierManagerExpanded.test.ts +142 -0
  297. package/tests/unit/transcriptAnalyzer.test.ts +64 -0
  298. package/tsconfig.json +25 -0
  299. package/vitest.config.ts +46 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * App.tsx View State & Integration Logic Tests
3
+ *
4
+ * Tests the view-state pattern and component wiring logic added to App.tsx.
5
+ * Since we're in a Node environment (no DOM), we test the logic layer:
6
+ * - View state transitions
7
+ * - mapPopoverState mapping
8
+ * - formatDuration helper
9
+ * - formatRelativeTime helper
10
+ * - State machine integration behavior
11
+ */
12
+
13
+ import { describe, it, expect } from 'vitest';
14
+ import type { SessionState } from '../../src/shared/types';
15
+
16
+ // ============================================================================
17
+ // Extract and test pure functions from App.tsx
18
+ // (These are module-scoped functions we can test without rendering)
19
+ // ============================================================================
20
+
21
+ // Replicate the pure functions from App.tsx for testing
22
+ function formatDuration(durationMs: number): string {
23
+ const seconds = Math.floor(durationMs / 1000);
24
+ const mins = Math.floor(seconds / 60);
25
+ const secs = seconds % 60;
26
+ return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
27
+ }
28
+
29
+ function formatRelativeTime(timestamp: number): string {
30
+ const now = Date.now();
31
+ const diff = now - timestamp;
32
+ const minute = 60_000;
33
+ const hour = 60 * minute;
34
+ const day = 24 * hour;
35
+
36
+ if (diff < minute) return 'just now';
37
+ if (diff < hour) return `${Math.floor(diff / minute)}m ago`;
38
+ if (diff < day) return `${Math.floor(diff / hour)}h ago`;
39
+ return `${Math.floor(diff / day)}d ago`;
40
+ }
41
+
42
+ function formatCaptureTrigger(trigger?: 'pause' | 'manual' | 'voice-command'): string {
43
+ switch (trigger) {
44
+ case 'manual':
45
+ return 'Manual Capture';
46
+ case 'voice-command':
47
+ return 'Voice Cue Capture';
48
+ default:
49
+ return 'Auto Pause Capture';
50
+ }
51
+ }
52
+
53
+ function mapPopoverState(state: SessionState): 'idle' | 'recording' | 'processing' | 'complete' | 'error' {
54
+ if (state === 'recording' || state === 'starting') return 'recording';
55
+ if (state === 'stopping' || state === 'processing') return 'processing';
56
+ if (state === 'complete') return 'complete';
57
+ if (state === 'error') return 'error';
58
+ return 'idle';
59
+ }
60
+
61
+ // ============================================================================
62
+ // AppView type definition (mirrors App.tsx)
63
+ // ============================================================================
64
+
65
+ type AppView = 'main' | 'settings' | 'history' | 'shortcuts';
66
+
67
+ // ============================================================================
68
+ // Tests
69
+ // ============================================================================
70
+
71
+ describe('formatDuration', () => {
72
+ it('formats zero duration', () => {
73
+ expect(formatDuration(0)).toBe('00:00');
74
+ });
75
+
76
+ it('formats seconds only', () => {
77
+ expect(formatDuration(45000)).toBe('00:45');
78
+ });
79
+
80
+ it('formats minutes and seconds', () => {
81
+ expect(formatDuration(125000)).toBe('02:05');
82
+ });
83
+
84
+ it('formats large durations', () => {
85
+ expect(formatDuration(3600000)).toBe('60:00');
86
+ });
87
+
88
+ it('truncates partial seconds', () => {
89
+ expect(formatDuration(1500)).toBe('00:01');
90
+ });
91
+ });
92
+
93
+ describe('formatRelativeTime', () => {
94
+ it('formats recent timestamps as "just now"', () => {
95
+ const recent = Date.now() - 30000; // 30 seconds ago
96
+ expect(formatRelativeTime(recent)).toBe('just now');
97
+ });
98
+
99
+ it('formats minutes ago', () => {
100
+ const fiveMinAgo = Date.now() - 300000;
101
+ expect(formatRelativeTime(fiveMinAgo)).toBe('5m ago');
102
+ });
103
+
104
+ it('formats hours ago', () => {
105
+ const twoHoursAgo = Date.now() - 7200000;
106
+ expect(formatRelativeTime(twoHoursAgo)).toBe('2h ago');
107
+ });
108
+
109
+ it('formats days ago', () => {
110
+ const threeDaysAgo = Date.now() - 259200000;
111
+ expect(formatRelativeTime(threeDaysAgo)).toBe('3d ago');
112
+ });
113
+ });
114
+
115
+ describe('formatCaptureTrigger', () => {
116
+ it('formats manual trigger', () => {
117
+ expect(formatCaptureTrigger('manual')).toBe('Manual Capture');
118
+ });
119
+
120
+ it('formats voice-command trigger', () => {
121
+ expect(formatCaptureTrigger('voice-command')).toBe('Voice Cue Capture');
122
+ });
123
+
124
+ it('formats undefined trigger as pause', () => {
125
+ expect(formatCaptureTrigger(undefined)).toBe('Auto Pause Capture');
126
+ });
127
+
128
+ it('formats pause trigger as auto', () => {
129
+ expect(formatCaptureTrigger('pause')).toBe('Auto Pause Capture');
130
+ });
131
+ });
132
+
133
+ describe('mapPopoverState', () => {
134
+ it('maps idle to idle', () => {
135
+ expect(mapPopoverState('idle')).toBe('idle');
136
+ });
137
+
138
+ it('maps starting to recording', () => {
139
+ expect(mapPopoverState('starting')).toBe('recording');
140
+ });
141
+
142
+ it('maps recording to recording', () => {
143
+ expect(mapPopoverState('recording')).toBe('recording');
144
+ });
145
+
146
+ it('maps stopping to processing', () => {
147
+ expect(mapPopoverState('stopping')).toBe('processing');
148
+ });
149
+
150
+ it('maps processing to processing', () => {
151
+ expect(mapPopoverState('processing')).toBe('processing');
152
+ });
153
+
154
+ it('maps complete to complete', () => {
155
+ expect(mapPopoverState('complete')).toBe('complete');
156
+ });
157
+
158
+ it('maps error to error', () => {
159
+ expect(mapPopoverState('error')).toBe('error');
160
+ });
161
+ });
162
+
163
+ describe('AppView type system', () => {
164
+ it('has valid view states', () => {
165
+ const views: AppView[] = ['main', 'settings', 'history', 'shortcuts'];
166
+ expect(views).toHaveLength(4);
167
+ });
168
+
169
+ it('view transitions are valid', () => {
170
+ // Simulate navigation flow
171
+ let currentView: AppView = 'main';
172
+
173
+ // Navigate to settings
174
+ currentView = 'settings';
175
+ expect(currentView).toBe('settings');
176
+
177
+ // Navigate back
178
+ currentView = 'main';
179
+ expect(currentView).toBe('main');
180
+
181
+ // Navigate to history
182
+ currentView = 'history';
183
+ expect(currentView).toBe('history');
184
+
185
+ // Navigate to shortcuts
186
+ currentView = 'shortcuts';
187
+ expect(currentView).toBe('shortcuts');
188
+ });
189
+
190
+ it('recording forces view back to main', () => {
191
+ // Simulate the behavior: when recording starts, currentView resets to main
192
+ let currentView: AppView = 'settings';
193
+ const sessionState: SessionState = 'recording';
194
+
195
+ // This mirrors the logic in App.tsx onStateChange handler
196
+ if (sessionState === 'recording') {
197
+ currentView = 'main';
198
+ }
199
+
200
+ expect(currentView).toBe('main');
201
+ });
202
+ });
203
+
204
+ describe('Component rendering conditions', () => {
205
+ it('CountdownTimer shows only when idle and countdown > 0', () => {
206
+ const state: SessionState = 'idle';
207
+ const countdownDuration = 3;
208
+ const showCountdown = true;
209
+
210
+ const shouldRender = showCountdown && countdownDuration > 0 && state === 'idle';
211
+ expect(shouldRender).toBe(true);
212
+ });
213
+
214
+ it('CountdownTimer hidden when countdown is 0', () => {
215
+ const countdownDuration = 0;
216
+ const showCountdown = true;
217
+
218
+ const shouldRender = showCountdown && countdownDuration > 0;
219
+ expect(shouldRender).toBe(false);
220
+ });
221
+
222
+ it('CompactAudioIndicator shows during recording when setting enabled', () => {
223
+ const state: SessionState = 'recording';
224
+ const showAudioWaveform = true;
225
+
226
+ const shouldRender = state === 'recording' && showAudioWaveform;
227
+ expect(shouldRender).toBe(true);
228
+ });
229
+
230
+ it('CompactAudioIndicator hidden when setting disabled', () => {
231
+ const state: SessionState = 'recording';
232
+ const showAudioWaveform = false;
233
+
234
+ const shouldRender = state === 'recording' && showAudioWaveform;
235
+ expect(shouldRender).toBe(false);
236
+ });
237
+
238
+ it('RecordingOverlay shows only during recording', () => {
239
+ const states: SessionState[] = ['idle', 'starting', 'recording', 'stopping', 'processing', 'complete', 'error'];
240
+ const results = states.map((s) => ({ state: s, shows: s === 'recording' }));
241
+
242
+ expect(results.find((r) => r.state === 'recording')?.shows).toBe(true);
243
+ expect(results.find((r) => r.state === 'idle')?.shows).toBe(false);
244
+ expect(results.find((r) => r.state === 'complete')?.shows).toBe(false);
245
+ });
246
+
247
+ it('SettingsPanel visibility tied to currentView', () => {
248
+ const views: AppView[] = ['main', 'settings', 'history', 'shortcuts'];
249
+ const settingsVisible = views.map((v) => v === 'settings');
250
+
251
+ expect(settingsVisible).toEqual([false, true, false, false]);
252
+ });
253
+
254
+ it('SessionHistory visibility tied to currentView', () => {
255
+ const views: AppView[] = ['main', 'settings', 'history', 'shortcuts'];
256
+ const historyVisible = views.map((v) => v === 'history');
257
+
258
+ expect(historyVisible).toEqual([false, false, true, false]);
259
+ });
260
+
261
+ it('KeyboardShortcuts visibility tied to currentView', () => {
262
+ const views: AppView[] = ['main', 'settings', 'history', 'shortcuts'];
263
+ const shortcutsVisible = views.map((v) => v === 'shortcuts');
264
+
265
+ expect(shortcutsVisible).toEqual([false, false, false, true]);
266
+ });
267
+
268
+ it('primary button disabled during transient states', () => {
269
+ const states: SessionState[] = ['idle', 'starting', 'recording', 'stopping', 'processing', 'complete', 'error'];
270
+ const disabled = states.map((s) => s === 'starting' || s === 'stopping' || s === 'processing');
271
+
272
+ expect(disabled).toEqual([false, true, false, true, true, false, false]);
273
+ });
274
+
275
+ it('manual capture only available during recording', () => {
276
+ const states: SessionState[] = ['idle', 'starting', 'recording', 'stopping', 'processing', 'complete', 'error'];
277
+ const available = states.map((s) => s === 'recording');
278
+
279
+ expect(available).toEqual([false, false, true, false, false, false, false]);
280
+ });
281
+ });
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { AUDIO_IPC_CHANNELS } from '../../src/main/audio/AudioCapture';
3
+ import { IPC_CHANNELS } from '../../src/shared/types';
4
+
5
+ describe('Audio IPC Channel Mapping', () => {
6
+ it('uses the shared IPC channel names expected by preload', () => {
7
+ expect(AUDIO_IPC_CHANNELS.REQUEST_DEVICES).toBe(IPC_CHANNELS.AUDIO_REQUEST_DEVICES);
8
+ expect(AUDIO_IPC_CHANNELS.START_CAPTURE).toBe(IPC_CHANNELS.AUDIO_START_CAPTURE);
9
+ expect(AUDIO_IPC_CHANNELS.STOP_CAPTURE).toBe(IPC_CHANNELS.AUDIO_STOP_CAPTURE);
10
+ expect(AUDIO_IPC_CHANNELS.SET_DEVICE).toBe(IPC_CHANNELS.AUDIO_SET_DEVICE);
11
+ expect(AUDIO_IPC_CHANNELS.AUDIO_CHUNK).toBe(IPC_CHANNELS.AUDIO_CHUNK);
12
+ expect(AUDIO_IPC_CHANNELS.DEVICES_RESPONSE).toBe(IPC_CHANNELS.AUDIO_DEVICES_RESPONSE);
13
+ expect(AUDIO_IPC_CHANNELS.CAPTURE_ERROR).toBe(IPC_CHANNELS.AUDIO_CAPTURE_ERROR);
14
+ expect(AUDIO_IPC_CHANNELS.CAPTURE_STARTED).toBe(IPC_CHANNELS.AUDIO_CAPTURE_STARTED);
15
+ expect(AUDIO_IPC_CHANNELS.CAPTURE_STOPPED).toBe(IPC_CHANNELS.AUDIO_CAPTURE_STOPPED);
16
+ });
17
+ });