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,600 @@
1
+ /**
2
+ * WindowsTaskbar - Windows-specific taskbar integration for markupr
3
+ *
4
+ * Provides native Windows taskbar features:
5
+ * - Jump lists with recent sessions and quick actions
6
+ * - Taskbar progress bar during export/processing
7
+ * - Overlay icons for recording/processing states
8
+ * - Thumbnail toolbar buttons for quick actions
9
+ * - Frame flashing for completion notifications
10
+ * - Custom thumbnail clip regions
11
+ *
12
+ * All methods are no-ops on non-Windows platforms.
13
+ */
14
+
15
+ import { BrowserWindow, app, nativeImage } from 'electron';
16
+ import { join } from 'path';
17
+
18
+ export interface RecentSession {
19
+ id: string;
20
+ name: string;
21
+ path: string;
22
+ date: Date;
23
+ }
24
+
25
+ export interface TaskbarActionCallback {
26
+ onRecord: () => void;
27
+ onStop: () => void;
28
+ onScreenshot: () => void;
29
+ onSettings: () => void;
30
+ }
31
+
32
+ type OverlayState = 'recording' | 'processing' | 'none';
33
+ type ProgressMode = 'none' | 'normal' | 'indeterminate' | 'error' | 'paused';
34
+
35
+ // Local interface for ThumbarButton since Electron types may vary
36
+ interface ThumbarButton {
37
+ tooltip: string;
38
+ icon: Electron.NativeImage;
39
+ flags?: ('enabled' | 'disabled' | 'dismissonclick' | 'nobackground' | 'hidden' | 'noninteractive')[];
40
+ click: () => void;
41
+ }
42
+
43
+ export class WindowsTaskbar {
44
+ private mainWindow: BrowserWindow;
45
+ private isWindows: boolean;
46
+ private recentSessions: RecentSession[] = [];
47
+ private currentOverlayState: OverlayState = 'none';
48
+ private isRecording = false;
49
+ private actionCallbacks: TaskbarActionCallback | null = null;
50
+ private assetsPath: string;
51
+
52
+ constructor(mainWindow: BrowserWindow) {
53
+ this.mainWindow = mainWindow;
54
+ this.isWindows = process.platform === 'win32';
55
+
56
+ // Assets are in build/ directory during development, resources/ in production
57
+ this.assetsPath = app.isPackaged
58
+ ? join(process.resourcesPath, 'build')
59
+ : join(app.getAppPath(), 'build');
60
+
61
+ if (!this.isWindows) {
62
+ console.log('[WindowsTaskbar] Not on Windows, taskbar features disabled');
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Set action callbacks for thumbnail toolbar buttons
68
+ */
69
+ setActionCallbacks(callbacks: TaskbarActionCallback): void {
70
+ this.actionCallbacks = callbacks;
71
+ }
72
+
73
+ /**
74
+ * Initialize the taskbar with default state
75
+ */
76
+ initialize(): void {
77
+ if (!this.isWindows) return;
78
+
79
+ console.log('[WindowsTaskbar] Initializing Windows taskbar integration');
80
+
81
+ this.setupJumpList();
82
+ this.setupThumbnailToolbar();
83
+ this.setOverlayIcon('none');
84
+ }
85
+
86
+ /**
87
+ * Set up Windows jump list with recent sessions and tasks
88
+ *
89
+ * Jump list structure:
90
+ * - Recent Sessions (user tasks)
91
+ * - Tasks (standard actions)
92
+ */
93
+ setupJumpList(): void {
94
+ if (!this.isWindows) return;
95
+
96
+ try {
97
+ const jumpListItems: Electron.JumpListCategory[] = [];
98
+
99
+ // Recent Sessions category
100
+ if (this.recentSessions.length > 0) {
101
+ const recentItems: Electron.JumpListItem[] = this.recentSessions
102
+ .slice(0, 10) // Max 10 recent items
103
+ .map((session) => ({
104
+ type: 'task' as const,
105
+ title: session.name,
106
+ description: `Opened ${session.date.toLocaleDateString()}`,
107
+ program: process.execPath,
108
+ args: `--open-session "${session.path}"`,
109
+ iconPath: process.execPath,
110
+ iconIndex: 0,
111
+ }));
112
+
113
+ jumpListItems.push({
114
+ type: 'custom',
115
+ name: 'Recent Sessions',
116
+ items: recentItems,
117
+ });
118
+ }
119
+
120
+ // Tasks category - quick actions
121
+ const tasks: Electron.JumpListItem[] = [
122
+ {
123
+ type: 'task',
124
+ title: 'New Recording',
125
+ description: 'Start a new feedback recording session',
126
+ program: process.execPath,
127
+ args: '--new-recording',
128
+ iconPath: process.execPath,
129
+ iconIndex: 0,
130
+ },
131
+ {
132
+ type: 'task',
133
+ title: 'Open Settings',
134
+ description: 'Configure markupr settings',
135
+ program: process.execPath,
136
+ args: '--settings',
137
+ iconPath: process.execPath,
138
+ iconIndex: 0,
139
+ },
140
+ {
141
+ type: 'task',
142
+ title: 'Check for Updates',
143
+ description: 'Check for application updates',
144
+ program: process.execPath,
145
+ args: '--check-updates',
146
+ iconPath: process.execPath,
147
+ iconIndex: 0,
148
+ },
149
+ ];
150
+
151
+ jumpListItems.push({
152
+ type: 'tasks',
153
+ items: tasks,
154
+ });
155
+
156
+ app.setJumpList(jumpListItems);
157
+ console.log('[WindowsTaskbar] Jump list configured');
158
+ } catch (error) {
159
+ console.error('[WindowsTaskbar] Failed to set jump list:', error);
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Update the list of recent sessions in the jump list
165
+ */
166
+ updateRecentSessions(sessions: RecentSession[]): void {
167
+ this.recentSessions = sessions;
168
+ this.setupJumpList();
169
+ }
170
+
171
+ /**
172
+ * Set taskbar progress bar
173
+ *
174
+ * @param progress - Progress value:
175
+ * - 0 to 1: Normal progress percentage
176
+ * - -1: Indeterminate (spinning) progress
177
+ * - 2: Clear/remove progress bar
178
+ */
179
+ setProgress(progress: number): void {
180
+ if (!this.isWindows) return;
181
+
182
+ try {
183
+ if (progress === 2 || progress < 0 && progress !== -1) {
184
+ // Clear progress bar
185
+ this.mainWindow.setProgressBar(-1);
186
+ } else if (progress === -1) {
187
+ // Indeterminate progress
188
+ this.mainWindow.setProgressBar(2, { mode: 'indeterminate' });
189
+ } else {
190
+ // Normal progress (0-1)
191
+ const clampedProgress = Math.max(0, Math.min(1, progress));
192
+ this.mainWindow.setProgressBar(clampedProgress, { mode: 'normal' });
193
+ }
194
+ } catch (error) {
195
+ console.error('[WindowsTaskbar] Failed to set progress:', error);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Set progress bar with specific mode
201
+ */
202
+ setProgressWithMode(progress: number, mode: ProgressMode): void {
203
+ if (!this.isWindows) return;
204
+
205
+ try {
206
+ if (mode === 'none') {
207
+ this.mainWindow.setProgressBar(-1);
208
+ } else {
209
+ const clampedProgress = Math.max(0, Math.min(1, progress));
210
+ this.mainWindow.setProgressBar(clampedProgress, { mode });
211
+ }
212
+ } catch (error) {
213
+ console.error('[WindowsTaskbar] Failed to set progress with mode:', error);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Clear the progress bar
219
+ */
220
+ clearProgress(): void {
221
+ this.setProgress(2);
222
+ }
223
+
224
+ /**
225
+ * Set overlay icon on the taskbar icon
226
+ * Used to indicate recording/processing state
227
+ */
228
+ setOverlayIcon(state: OverlayState): void {
229
+ if (!this.isWindows) return;
230
+
231
+ this.currentOverlayState = state;
232
+
233
+ try {
234
+ if (state === 'none') {
235
+ this.mainWindow.setOverlayIcon(null, '');
236
+ return;
237
+ }
238
+
239
+ const iconName = state === 'recording'
240
+ ? 'overlay-recording.png'
241
+ : 'overlay-processing.png';
242
+
243
+ const iconPath = join(this.assetsPath, iconName);
244
+
245
+ try {
246
+ const icon = nativeImage.createFromPath(iconPath);
247
+
248
+ if (icon.isEmpty()) {
249
+ // Fallback: create a simple colored icon programmatically
250
+ const fallbackIcon = this.createFallbackOverlayIcon(state);
251
+ const description = state === 'recording' ? 'Recording' : 'Processing';
252
+ this.mainWindow.setOverlayIcon(fallbackIcon, description);
253
+ console.log(`[WindowsTaskbar] Using fallback overlay icon for ${state}`);
254
+ } else {
255
+ const description = state === 'recording' ? 'Recording' : 'Processing';
256
+ this.mainWindow.setOverlayIcon(icon, description);
257
+ }
258
+ } catch {
259
+ // If icon file doesn't exist, create fallback
260
+ const fallbackIcon = this.createFallbackOverlayIcon(state);
261
+ const description = state === 'recording' ? 'Recording' : 'Processing';
262
+ this.mainWindow.setOverlayIcon(fallbackIcon, description);
263
+ console.log(`[WindowsTaskbar] Icon not found, using fallback for ${state}`);
264
+ }
265
+ } catch (error) {
266
+ console.error('[WindowsTaskbar] Failed to set overlay icon:', error);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Create a fallback overlay icon programmatically
272
+ */
273
+ private createFallbackOverlayIcon(state: OverlayState): Electron.NativeImage {
274
+ // Create a 16x16 icon using data URL
275
+ const size = 16;
276
+ const color = state === 'recording' ? '#FF4444' : '#4488FF';
277
+
278
+ // Simple SVG circle
279
+ const svg = `
280
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
281
+ <circle cx="${size/2}" cy="${size/2}" r="${size/2 - 1}" fill="${color}" />
282
+ </svg>
283
+ `;
284
+
285
+ const base64 = Buffer.from(svg).toString('base64');
286
+ return nativeImage.createFromDataURL(`data:image/svg+xml;base64,${base64}`);
287
+ }
288
+
289
+ /**
290
+ * Set up thumbnail toolbar buttons
291
+ * These appear when hovering over the taskbar icon
292
+ */
293
+ setupThumbnailToolbar(): void {
294
+ if (!this.isWindows) return;
295
+
296
+ try {
297
+ const buttons = this.createThumbnailButtons();
298
+ this.mainWindow.setThumbarButtons(buttons);
299
+ console.log('[WindowsTaskbar] Thumbnail toolbar configured');
300
+ } catch (error) {
301
+ console.error('[WindowsTaskbar] Failed to set thumbnail toolbar:', error);
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Update thumbnail toolbar based on recording state
307
+ */
308
+ updateThumbnailToolbar(isRecording: boolean): void {
309
+ if (!this.isWindows) return;
310
+
311
+ this.isRecording = isRecording;
312
+
313
+ try {
314
+ const buttons = this.createThumbnailButtons();
315
+ this.mainWindow.setThumbarButtons(buttons);
316
+ } catch (error) {
317
+ console.error('[WindowsTaskbar] Failed to update thumbnail toolbar:', error);
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Create thumbnail toolbar buttons based on current state
323
+ */
324
+ private createThumbnailButtons(): ThumbarButton[] {
325
+ const buttons: ThumbarButton[] = [];
326
+
327
+ // Record/Stop button
328
+ if (this.isRecording) {
329
+ buttons.push({
330
+ tooltip: 'Stop Recording',
331
+ icon: this.loadToolbarIcon('toolbar-stop.png'),
332
+ click: () => {
333
+ console.log('[WindowsTaskbar] Stop button clicked');
334
+ this.actionCallbacks?.onStop();
335
+ },
336
+ });
337
+ } else {
338
+ buttons.push({
339
+ tooltip: 'Start Recording',
340
+ icon: this.loadToolbarIcon('toolbar-record.png'),
341
+ click: () => {
342
+ console.log('[WindowsTaskbar] Record button clicked');
343
+ this.actionCallbacks?.onRecord();
344
+ },
345
+ });
346
+ }
347
+
348
+ // Screenshot button (only enabled during recording)
349
+ buttons.push({
350
+ tooltip: 'Take Screenshot',
351
+ icon: this.loadToolbarIcon('toolbar-screenshot.png'),
352
+ flags: this.isRecording ? [] : ['disabled'],
353
+ click: () => {
354
+ console.log('[WindowsTaskbar] Screenshot button clicked');
355
+ this.actionCallbacks?.onScreenshot();
356
+ },
357
+ });
358
+
359
+ // Settings button
360
+ buttons.push({
361
+ tooltip: 'Settings',
362
+ icon: this.loadToolbarIcon('toolbar-settings.png'),
363
+ click: () => {
364
+ console.log('[WindowsTaskbar] Settings button clicked');
365
+ this.actionCallbacks?.onSettings();
366
+ },
367
+ });
368
+
369
+ return buttons;
370
+ }
371
+
372
+ /**
373
+ * Load a toolbar icon, with fallback to programmatic icon
374
+ */
375
+ private loadToolbarIcon(iconName: string): Electron.NativeImage {
376
+ const iconPath = join(this.assetsPath, iconName);
377
+
378
+ try {
379
+ const icon = nativeImage.createFromPath(iconPath);
380
+
381
+ if (!icon.isEmpty()) {
382
+ return icon;
383
+ }
384
+ } catch {
385
+ // Fall through to create fallback
386
+ }
387
+
388
+ // Create fallback icon
389
+ return this.createFallbackToolbarIcon(iconName);
390
+ }
391
+
392
+ /**
393
+ * Create a fallback toolbar icon programmatically
394
+ */
395
+ private createFallbackToolbarIcon(iconName: string): Electron.NativeImage {
396
+ const size = 16;
397
+ let svgContent: string;
398
+
399
+ if (iconName.includes('record')) {
400
+ // Red circle for record
401
+ svgContent = `
402
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
403
+ <circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="#FF4444" />
404
+ </svg>
405
+ `;
406
+ } else if (iconName.includes('stop')) {
407
+ // White square for stop
408
+ svgContent = `
409
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
410
+ <rect x="3" y="3" width="${size - 6}" height="${size - 6}" fill="#FFFFFF" />
411
+ </svg>
412
+ `;
413
+ } else if (iconName.includes('screenshot')) {
414
+ // Camera-like icon for screenshot
415
+ svgContent = `
416
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
417
+ <rect x="2" y="4" width="12" height="9" rx="1" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
418
+ <circle cx="8" cy="8" r="2" fill="#FFFFFF"/>
419
+ </svg>
420
+ `;
421
+ } else if (iconName.includes('settings')) {
422
+ // Gear-like icon for settings
423
+ svgContent = `
424
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
425
+ <circle cx="${size/2}" cy="${size/2}" r="3" fill="none" stroke="#FFFFFF" stroke-width="1.5"/>
426
+ <circle cx="${size/2}" cy="${size/2}" r="6" fill="none" stroke="#FFFFFF" stroke-width="1" stroke-dasharray="2 2"/>
427
+ </svg>
428
+ `;
429
+ } else {
430
+ // Default: simple white circle
431
+ svgContent = `
432
+ <svg width="${size}" height="${size}" xmlns="http://www.w3.org/2000/svg">
433
+ <circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="#FFFFFF" />
434
+ </svg>
435
+ `;
436
+ }
437
+
438
+ const base64 = Buffer.from(svgContent).toString('base64');
439
+ return nativeImage.createFromDataURL(`data:image/svg+xml;base64,${base64}`);
440
+ }
441
+
442
+ /**
443
+ * Flash the taskbar button to get user attention
444
+ *
445
+ * @param count - Number of times to flash (0 for continuous until focused)
446
+ */
447
+ flashFrame(count?: number): void {
448
+ if (!this.isWindows) return;
449
+
450
+ try {
451
+ if (count === 0 || count === undefined) {
452
+ // Flash until the window is focused
453
+ this.mainWindow.flashFrame(true);
454
+
455
+ // Stop flashing when window gains focus
456
+ const stopFlashing = (): void => {
457
+ this.mainWindow.flashFrame(false);
458
+ this.mainWindow.removeListener('focus', stopFlashing);
459
+ };
460
+ this.mainWindow.once('focus', stopFlashing);
461
+ } else {
462
+ // Flash a specific number of times
463
+ let flashCount = 0;
464
+ const interval = setInterval(() => {
465
+ if (flashCount >= count * 2) {
466
+ clearInterval(interval);
467
+ this.mainWindow.flashFrame(false);
468
+ return;
469
+ }
470
+
471
+ this.mainWindow.flashFrame(flashCount % 2 === 0);
472
+ flashCount++;
473
+ }, 500);
474
+ }
475
+ } catch (error) {
476
+ console.error('[WindowsTaskbar] Failed to flash frame:', error);
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Stop flashing the taskbar button
482
+ */
483
+ stopFlashing(): void {
484
+ if (!this.isWindows) return;
485
+
486
+ try {
487
+ this.mainWindow.flashFrame(false);
488
+ } catch (error) {
489
+ console.error('[WindowsTaskbar] Failed to stop flashing:', error);
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Set a custom thumbnail clip region
495
+ * Used to show a specific part of the window in the thumbnail preview
496
+ *
497
+ * @param region - Rectangle defining the clip region, or undefined to reset
498
+ */
499
+ setThumbnailClip(region?: Electron.Rectangle): void {
500
+ if (!this.isWindows) return;
501
+
502
+ try {
503
+ if (region) {
504
+ this.mainWindow.setThumbnailClip(region);
505
+ } else {
506
+ // Reset to full window
507
+ this.mainWindow.setThumbnailClip({ x: 0, y: 0, width: 0, height: 0 });
508
+ }
509
+ } catch (error) {
510
+ console.error('[WindowsTaskbar] Failed to set thumbnail clip:', error);
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Update the taskbar state based on session state.
516
+ * Supports the bulletproof 7-state machine.
517
+ */
518
+ updateSessionState(
519
+ state: 'idle' | 'starting' | 'recording' | 'stopping' | 'processing' | 'complete' | 'error'
520
+ ): void {
521
+ switch (state) {
522
+ case 'idle':
523
+ this.setOverlayIcon('none');
524
+ this.clearProgress();
525
+ this.updateThumbnailToolbar(false);
526
+ break;
527
+ case 'starting':
528
+ this.setOverlayIcon('processing');
529
+ this.setProgress(-1); // Indeterminate while starting
530
+ this.updateThumbnailToolbar(false);
531
+ break;
532
+ case 'recording':
533
+ this.setOverlayIcon('recording');
534
+ this.clearProgress();
535
+ this.updateThumbnailToolbar(true);
536
+ break;
537
+ case 'stopping':
538
+ this.setOverlayIcon('processing');
539
+ this.setProgress(-1); // Indeterminate while stopping
540
+ this.updateThumbnailToolbar(false);
541
+ break;
542
+ case 'processing':
543
+ this.setOverlayIcon('processing');
544
+ this.setProgress(-1); // Indeterminate
545
+ this.updateThumbnailToolbar(false);
546
+ break;
547
+ case 'complete':
548
+ this.setOverlayIcon('none');
549
+ this.clearProgress();
550
+ this.updateThumbnailToolbar(false);
551
+ this.flashFrame(3); // Flash 3 times on completion
552
+ break;
553
+ case 'error':
554
+ this.setOverlayIcon('none');
555
+ this.clearProgress();
556
+ this.updateThumbnailToolbar(false);
557
+ this.flashFrame(2); // Flash 2 times on error
558
+ break;
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Clean up taskbar resources
564
+ */
565
+ destroy(): void {
566
+ if (!this.isWindows) return;
567
+
568
+ try {
569
+ // Clear overlay icon
570
+ this.mainWindow.setOverlayIcon(null, '');
571
+
572
+ // Clear progress
573
+ this.mainWindow.setProgressBar(-1);
574
+
575
+ // Clear thumbnail toolbar
576
+ this.mainWindow.setThumbarButtons([]);
577
+
578
+ // Stop any flashing
579
+ this.mainWindow.flashFrame(false);
580
+
581
+ console.log('[WindowsTaskbar] Cleaned up');
582
+ } catch (error) {
583
+ console.error('[WindowsTaskbar] Error during cleanup:', error);
584
+ }
585
+ }
586
+ }
587
+
588
+ // Export singleton factory
589
+ let windowsTaskbarInstance: WindowsTaskbar | null = null;
590
+
591
+ export function createWindowsTaskbar(mainWindow: BrowserWindow): WindowsTaskbar {
592
+ windowsTaskbarInstance = new WindowsTaskbar(mainWindow);
593
+ return windowsTaskbarInstance;
594
+ }
595
+
596
+ export function getWindowsTaskbar(): WindowsTaskbar | null {
597
+ return windowsTaskbarInstance;
598
+ }
599
+
600
+ export default WindowsTaskbar;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Platform-specific integrations for markupr
3
+ *
4
+ * Provides native OS features:
5
+ * - Windows: Taskbar integration (jump lists, progress, overlay icons, thumbnail toolbar)
6
+ * - macOS: Dock integration (handled by TrayManager and MenuManager)
7
+ * - Linux: Unity launcher (future)
8
+ */
9
+
10
+ export {
11
+ WindowsTaskbar,
12
+ createWindowsTaskbar,
13
+ getWindowsTaskbar,
14
+ type RecentSession,
15
+ type TaskbarActionCallback,
16
+ } from './WindowsTaskbar';