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,56 @@
1
+ /**
2
+ * Transcription Module
3
+ *
4
+ * Transcription Tiers (post-process architecture):
5
+ * - Tier 1: Local Whisper (default, post-session batch)
6
+ * - Tier 2: Timer-only (fallback, no transcription)
7
+ *
8
+ * The TierManager tracks tier availability for the UI.
9
+ * App works WITHOUT any API keys using local Whisper.
10
+ */
11
+
12
+ // ============================================================================
13
+ // Primary API - TierManager (use this for transcription)
14
+ // ============================================================================
15
+
16
+ export { TierManager, tierManager } from './TierManager';
17
+
18
+ // ============================================================================
19
+ // Supporting Services
20
+ // ============================================================================
21
+ // Whisper (Tier 1)
22
+ export { WhisperService, whisperService } from './WhisperService';
23
+
24
+ // Model Management
25
+ export { ModelDownloadManager, modelDownloadManager } from './ModelDownloadManager';
26
+
27
+ // ============================================================================
28
+ // Types
29
+ // ============================================================================
30
+
31
+ export type {
32
+ // Tier types
33
+ TranscriptionTier,
34
+ WhisperModel,
35
+ TierStatus,
36
+ TierQuality,
37
+ // Event types
38
+ TranscriptEvent,
39
+ PauseEvent,
40
+ WhisperTranscriptResult,
41
+ WhisperConfig,
42
+ // Model types
43
+ ModelInfo,
44
+ DownloadProgress,
45
+ DownloadResult,
46
+ // Callbacks
47
+ TranscriptCallback,
48
+ PauseCallback,
49
+ TierChangeCallback,
50
+ ErrorCallback,
51
+ ProgressCallback,
52
+ CompleteCallback,
53
+ } from './types';
54
+
55
+ // Re-export types from shared for convenience
56
+ export type { TranscriptionSegment } from '../../shared/types';
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Shared Types for Transcription Services
3
+ *
4
+ * Available tiers (post-process architecture):
5
+ * - Tier 1: Local Whisper (post-session batch transcription)
6
+ * - Tier 2: Timer-only (fallback, no transcription)
7
+ */
8
+
9
+ // ============================================================================
10
+ // Transcription Tier Types
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Available transcription tiers in priority order
15
+ */
16
+ export type TranscriptionTier = 'whisper' | 'timer-only';
17
+
18
+ /**
19
+ * Whisper model sizes available for download
20
+ */
21
+ export type WhisperModel = 'tiny' | 'base' | 'small' | 'medium' | 'large';
22
+
23
+ /**
24
+ * Status of a transcription tier
25
+ */
26
+ export interface TierStatus {
27
+ tier: TranscriptionTier;
28
+ available: boolean;
29
+ reason?: string;
30
+ }
31
+
32
+ /**
33
+ * Quality information for a tier
34
+ */
35
+ export interface TierQuality {
36
+ accuracy: string;
37
+ latency: string;
38
+ }
39
+
40
+ // ============================================================================
41
+ // Transcript Event Types
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Unified transcript event from any tier
46
+ */
47
+ export interface TranscriptEvent {
48
+ text: string;
49
+ isFinal: boolean;
50
+ confidence: number;
51
+ timestamp: number;
52
+ tier: TranscriptionTier;
53
+ }
54
+
55
+ /**
56
+ * Pause detection event (triggers screenshots)
57
+ */
58
+ export interface PauseEvent {
59
+ timestamp: number;
60
+ tier: TranscriptionTier;
61
+ }
62
+
63
+ // ============================================================================
64
+ // Whisper-Specific Types
65
+ // ============================================================================
66
+
67
+ /**
68
+ * Result from Whisper transcription
69
+ */
70
+ export interface WhisperTranscriptResult {
71
+ text: string;
72
+ startTime: number;
73
+ endTime: number;
74
+ confidence: number;
75
+ }
76
+
77
+ /**
78
+ * Whisper service configuration
79
+ */
80
+ export interface WhisperConfig {
81
+ modelPath: string;
82
+ language: string;
83
+ threads: number;
84
+ translateToEnglish: boolean;
85
+ }
86
+
87
+ // ============================================================================
88
+ // Model Download Types
89
+ // ============================================================================
90
+
91
+ /**
92
+ * Information about a Whisper model
93
+ */
94
+ export interface ModelInfo {
95
+ name: WhisperModel;
96
+ filename: string;
97
+ sizeBytes: number;
98
+ sizeMB: number;
99
+ ramRequired: string;
100
+ quality: string;
101
+ url: string;
102
+ }
103
+
104
+ /**
105
+ * Progress information during model download
106
+ */
107
+ export interface DownloadProgress {
108
+ model: WhisperModel;
109
+ downloadedBytes: number;
110
+ totalBytes: number;
111
+ percent: number;
112
+ speedBps: number;
113
+ estimatedSecondsRemaining: number;
114
+ }
115
+
116
+ /**
117
+ * Result of a model download operation
118
+ */
119
+ export interface DownloadResult {
120
+ success: boolean;
121
+ model: WhisperModel;
122
+ path: string;
123
+ error?: string;
124
+ }
125
+
126
+ // ============================================================================
127
+ // Callback Types
128
+ // ============================================================================
129
+
130
+ export type TranscriptCallback = (event: TranscriptEvent) => void;
131
+ export type PauseCallback = (event: PauseEvent) => void;
132
+ export type TierChangeCallback = (oldTier: TranscriptionTier, newTier: TranscriptionTier, reason: string) => void;
133
+ export type ErrorCallback = (error: Error, tier?: TranscriptionTier) => void;
134
+ export type ProgressCallback = (progress: DownloadProgress) => void;
135
+ export type CompleteCallback = (result: DownloadResult) => void;
@@ -0,0 +1,284 @@
1
+ /**
2
+ * PopoverManager - NSPopover-like window for menu bar apps
3
+ *
4
+ * Creates a frameless window that:
5
+ * - Appears anchored below the tray icon
6
+ * - Closes when clicking outside (blur)
7
+ * - Has no shadow (draws its own)
8
+ * - Uses vibrancy on macOS
9
+ * - Supports different sizes per state
10
+ */
11
+
12
+ import { BrowserWindow, screen, Tray, app } from 'electron';
13
+ import { join } from 'path';
14
+
15
+ /**
16
+ * Popover sizes for different application states
17
+ */
18
+ export const POPOVER_SIZES = {
19
+ idle: { width: 460, height: 680 },
20
+ recording: { width: 316, height: 90 },
21
+ processing: { width: 320, height: 140 },
22
+ complete: { width: 460, height: 720 },
23
+ settings: { width: 400, height: 520 },
24
+ error: { width: 440, height: 620 },
25
+ } as const;
26
+
27
+ export type PopoverState = keyof typeof POPOVER_SIZES;
28
+
29
+ export interface PopoverConfig {
30
+ width: number;
31
+ height: number;
32
+ tray: Tray;
33
+ }
34
+
35
+ /**
36
+ * PopoverManager class - Manages the menu bar popover window
37
+ */
38
+ export class PopoverManager {
39
+ private window: BrowserWindow | null = null;
40
+ private tray: Tray | null = null;
41
+ private config: PopoverConfig;
42
+ private keepVisibleOnBlur = false;
43
+ private currentState: PopoverState = 'idle';
44
+
45
+ constructor(config: PopoverConfig) {
46
+ this.config = config;
47
+ this.tray = config.tray;
48
+ }
49
+
50
+ /**
51
+ * Create the popover window with proper configuration
52
+ */
53
+ create(): BrowserWindow {
54
+ const preloadPath = join(app.getAppPath(), 'dist', 'preload', 'index.mjs');
55
+
56
+ this.window = new BrowserWindow({
57
+ width: this.config.width,
58
+ height: this.config.height,
59
+ show: false,
60
+ frame: false,
61
+ fullscreenable: false,
62
+ resizable: false,
63
+ movable: false,
64
+ minimizable: false,
65
+ maximizable: false,
66
+ closable: true,
67
+ alwaysOnTop: true,
68
+ skipTaskbar: true,
69
+
70
+ // macOS-specific vibrancy for native feel
71
+ ...(process.platform === 'darwin' && {
72
+ vibrancy: 'popover',
73
+ visualEffectState: 'active',
74
+ transparent: true,
75
+ backgroundColor: '#00000000',
76
+ }),
77
+
78
+ // Windows/Linux fallback with transparency
79
+ ...(process.platform !== 'darwin' && {
80
+ transparent: true,
81
+ backgroundColor: '#00000000',
82
+ }),
83
+
84
+ webPreferences: {
85
+ preload: preloadPath,
86
+ nodeIntegration: false,
87
+ contextIsolation: true,
88
+ sandbox: false,
89
+ },
90
+ });
91
+
92
+ // Hide on blur (clicking outside the popover)
93
+ this.window.on('blur', () => {
94
+ if (this.keepVisibleOnBlur) {
95
+ return;
96
+ }
97
+ this.hide();
98
+ });
99
+
100
+ // Prevent window from showing in Mission Control on macOS
101
+ if (process.platform === 'darwin') {
102
+ this.window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
103
+ }
104
+
105
+ console.log('[PopoverManager] Popover window created');
106
+
107
+ return this.window;
108
+ }
109
+
110
+ /**
111
+ * Show the popover anchored to the tray icon
112
+ */
113
+ show(): void {
114
+ if (!this.window || !this.tray) return;
115
+
116
+ const position = this.calculatePosition();
117
+ this.window.setPosition(position.x, position.y, false);
118
+ this.window.show();
119
+ this.window.focus();
120
+
121
+ console.log('[PopoverManager] Popover shown at', position);
122
+ }
123
+
124
+ /**
125
+ * Hide the popover
126
+ */
127
+ hide(): void {
128
+ if (!this.window) return;
129
+ this.window.hide();
130
+ console.log('[PopoverManager] Popover hidden');
131
+ }
132
+
133
+ /**
134
+ * Toggle popover visibility
135
+ */
136
+ toggle(): void {
137
+ if (!this.window) return;
138
+
139
+ if (this.window.isVisible()) {
140
+ this.hide();
141
+ } else {
142
+ this.show();
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Calculate position to anchor below tray icon
148
+ * Handles multi-monitor setups and taskbar positions
149
+ */
150
+ private calculatePosition(): { x: number; y: number } {
151
+ if (!this.tray || !this.window) {
152
+ return { x: 0, y: 0 };
153
+ }
154
+
155
+ const trayBounds = this.tray.getBounds();
156
+ const windowBounds = this.window.getBounds();
157
+ const isHudState = this.currentState === 'recording' || this.currentState === 'processing';
158
+
159
+ if (isHudState) {
160
+ const cursorDisplay = screen.getDisplayNearestPoint(screen.getCursorScreenPoint());
161
+ const workArea = cursorDisplay.workArea;
162
+ const x = Math.round(workArea.x + (workArea.width - windowBounds.width) / 2);
163
+ const y = Math.round(workArea.y + 8);
164
+ return { x, y };
165
+ }
166
+
167
+ const display = screen.getDisplayMatching(trayBounds);
168
+
169
+ // Center horizontally under tray icon
170
+ let x = Math.round(trayBounds.x + (trayBounds.width / 2) - (windowBounds.width / 2));
171
+
172
+ // Position below tray (macOS menu bar is at top)
173
+ let y: number;
174
+ if (process.platform === 'darwin') {
175
+ // macOS: position below menu bar with small gap
176
+ y = trayBounds.y + trayBounds.height + 4;
177
+ } else {
178
+ // Windows/Linux: detect taskbar position
179
+ const taskbarOnTop = trayBounds.y < display.workArea.y + 50;
180
+ if (taskbarOnTop) {
181
+ // Taskbar at top - position below it
182
+ y = trayBounds.y + trayBounds.height + 4;
183
+ } else {
184
+ // Taskbar at bottom - position above it
185
+ y = trayBounds.y - windowBounds.height - 4;
186
+ }
187
+ }
188
+
189
+ // Ensure window stays on screen (respects work area boundaries)
190
+ const { workArea } = display;
191
+ x = Math.max(workArea.x, Math.min(x, workArea.x + workArea.width - windowBounds.width));
192
+ y = Math.max(workArea.y, Math.min(y, workArea.y + workArea.height - windowBounds.height));
193
+
194
+ return { x, y };
195
+ }
196
+
197
+ /**
198
+ * Get the BrowserWindow for loading content
199
+ */
200
+ getWindow(): BrowserWindow | null {
201
+ return this.window;
202
+ }
203
+
204
+ /**
205
+ * Check if popover is visible
206
+ */
207
+ isVisible(): boolean {
208
+ return this.window?.isVisible() ?? false;
209
+ }
210
+
211
+ /**
212
+ * Resize the popover for different states
213
+ * Re-anchors to tray if visible
214
+ */
215
+ resize(width: number, height: number): void {
216
+ if (!this.window) return;
217
+
218
+ this.window.setSize(width, height, true);
219
+
220
+ // Reposition to stay anchored to tray
221
+ if (this.window.isVisible()) {
222
+ const position = this.calculatePosition();
223
+ this.window.setPosition(position.x, position.y, false);
224
+ }
225
+
226
+ console.log(`[PopoverManager] Resized to ${width}x${height}`);
227
+ }
228
+
229
+ /**
230
+ * Resize to a predefined state size
231
+ */
232
+ resizeToState(state: PopoverState): void {
233
+ this.currentState = state;
234
+ this.applyStateAppearance(state);
235
+ const size = POPOVER_SIZES[state];
236
+ this.resize(size.width, size.height);
237
+ }
238
+
239
+ /**
240
+ * Keep popover visible while app focus changes.
241
+ * Useful during active recording when users switch to other apps.
242
+ */
243
+ setKeepVisibleOnBlur(enabled: boolean): void {
244
+ this.keepVisibleOnBlur = enabled;
245
+ }
246
+
247
+ /**
248
+ * Update the tray reference (if tray is recreated)
249
+ */
250
+ setTray(tray: Tray): void {
251
+ this.tray = tray;
252
+ }
253
+
254
+ /**
255
+ * Clean up resources
256
+ */
257
+ destroy(): void {
258
+ if (this.window) {
259
+ this.window.destroy();
260
+ this.window = null;
261
+ }
262
+ this.tray = null;
263
+ console.log('[PopoverManager] Destroyed');
264
+ }
265
+
266
+ private applyStateAppearance(state: PopoverState): void {
267
+ if (!this.window || process.platform !== 'darwin') {
268
+ return;
269
+ }
270
+
271
+ const isHudState = state === 'recording' || state === 'processing';
272
+ try {
273
+ // During compact HUD states, disable popover vibrancy + shadow so only
274
+ // the overlay chip itself is visible (no boundary rectangle).
275
+ this.window.setVibrancy(isHudState ? null : 'popover');
276
+ this.window.setBackgroundColor('#00000000');
277
+ if (typeof this.window.setHasShadow === 'function') {
278
+ this.window.setHasShadow(!isHudState);
279
+ }
280
+ } catch (error) {
281
+ console.warn('[PopoverManager] Failed to apply state appearance:', error);
282
+ }
283
+ }
284
+ }