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,377 @@
1
+ /**
2
+ * markupr - Update Notification Component
3
+ *
4
+ * Shows update status notifications to the user with:
5
+ * - Update available banner with release notes
6
+ * - Download progress indicator
7
+ * - Ready to install prompt
8
+ *
9
+ * User controls when to download and when to restart.
10
+ */
11
+
12
+ import React, { useState, useEffect, useCallback } from 'react';
13
+ import type { UpdateStatusPayload, UpdateStatusType } from '../../shared/types';
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ interface UpdateState {
20
+ status: UpdateStatusType;
21
+ version?: string;
22
+ releaseNotes?: string | null;
23
+ percent?: number;
24
+ message?: string;
25
+ }
26
+
27
+ // =============================================================================
28
+ // Component
29
+ // =============================================================================
30
+
31
+ export function UpdateNotification(): React.ReactElement | null {
32
+ const [update, setUpdate] = useState<UpdateState>({ status: 'idle' });
33
+ const [isExpanded, setIsExpanded] = useState(false);
34
+ const [isDismissed, setIsDismissed] = useState(false);
35
+
36
+ // Subscribe to update status events
37
+ useEffect(() => {
38
+ let isMounted = true;
39
+ let unsubscribe: (() => void) | undefined;
40
+
41
+ const init = async (): Promise<void> => {
42
+ try {
43
+ const checkForUpdates = await window.markupr.settings.get('checkForUpdates');
44
+ if (!checkForUpdates || !isMounted) {
45
+ setIsDismissed(true);
46
+ return;
47
+ }
48
+ } catch {
49
+ // If settings can't be read, keep default behavior.
50
+ }
51
+
52
+ unsubscribe = window.markupr.updates.onStatus((status: UpdateStatusPayload) => {
53
+ // Local/manual builds may not ship updater metadata; suppress that noisy non-actionable state.
54
+ if (
55
+ status.status === 'error' &&
56
+ typeof status.message === 'string' &&
57
+ /(app-update\.yml|latest\.yml|enoent)/i.test(status.message)
58
+ ) {
59
+ setUpdate({ status: 'not-available' });
60
+ setIsDismissed(true);
61
+ return;
62
+ }
63
+
64
+ setUpdate({
65
+ status: status.status,
66
+ version: status.version,
67
+ releaseNotes: status.releaseNotes,
68
+ percent: status.percent,
69
+ message: status.message,
70
+ });
71
+
72
+ // Auto-expand for important states
73
+ if (status.status === 'available' || status.status === 'ready') {
74
+ setIsExpanded(true);
75
+ setIsDismissed(false);
76
+ }
77
+ });
78
+
79
+ window.markupr.updates.check();
80
+ };
81
+
82
+ void init();
83
+
84
+ return () => {
85
+ isMounted = false;
86
+ unsubscribe?.();
87
+ };
88
+ }, []);
89
+
90
+ // Handle download button click
91
+ const handleDownload = useCallback(() => {
92
+ window.markupr.updates.download();
93
+ }, []);
94
+
95
+ // Handle install/restart button click
96
+ const handleInstall = useCallback(() => {
97
+ window.markupr.updates.install();
98
+ }, []);
99
+
100
+ // Handle dismiss (Later button)
101
+ const handleDismiss = useCallback(() => {
102
+ setIsDismissed(true);
103
+ }, []);
104
+
105
+ // Handle toggle expand/collapse
106
+ const handleToggleExpand = useCallback(() => {
107
+ setIsExpanded((prev) => !prev);
108
+ }, []);
109
+
110
+ // Don't render if idle, checking, not-available, or dismissed
111
+ if (
112
+ update.status === 'idle' ||
113
+ update.status === 'checking' ||
114
+ update.status === 'not-available' ||
115
+ (update.status === 'error' && !update.message) ||
116
+ isDismissed
117
+ ) {
118
+ return null;
119
+ }
120
+
121
+ return (
122
+ <div className="fixed bottom-4 right-4 max-w-sm z-50 animate-slide-up">
123
+ {/* Update Available State */}
124
+ {update.status === 'available' && (
125
+ <div className="bg-gradient-to-br from-blue-600 to-blue-700 text-white p-4 rounded-xl shadow-2xl border border-blue-500/30">
126
+ {/* Header */}
127
+ <div className="flex items-start justify-between gap-3">
128
+ <div className="flex items-center gap-2">
129
+ <div className="w-8 h-8 bg-white/20 rounded-lg flex items-center justify-center">
130
+ <svg
131
+ className="w-5 h-5"
132
+ fill="none"
133
+ stroke="currentColor"
134
+ viewBox="0 0 24 24"
135
+ >
136
+ <path
137
+ strokeLinecap="round"
138
+ strokeLinejoin="round"
139
+ strokeWidth={2}
140
+ d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
141
+ />
142
+ </svg>
143
+ </div>
144
+ <div>
145
+ <h4 className="font-semibold text-sm">Update Available</h4>
146
+ <p className="text-xs text-white/80">Version {update.version}</p>
147
+ </div>
148
+ </div>
149
+ <button
150
+ onClick={handleToggleExpand}
151
+ className="p-1 hover:bg-white/10 rounded transition-colors"
152
+ aria-label={isExpanded ? 'Collapse' : 'Expand'}
153
+ >
154
+ <svg
155
+ className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
156
+ fill="none"
157
+ stroke="currentColor"
158
+ viewBox="0 0 24 24"
159
+ >
160
+ <path
161
+ strokeLinecap="round"
162
+ strokeLinejoin="round"
163
+ strokeWidth={2}
164
+ d="M19 9l-7 7-7-7"
165
+ />
166
+ </svg>
167
+ </button>
168
+ </div>
169
+
170
+ {/* Release Notes (Expandable) */}
171
+ {isExpanded && update.releaseNotes && (
172
+ <div className="mt-3 p-3 bg-black/20 rounded-lg text-sm max-h-32 overflow-y-auto">
173
+ <h5 className="font-medium text-xs text-white/70 uppercase tracking-wide mb-2">
174
+ {"What's New"}
175
+ </h5>
176
+ <div
177
+ className="prose prose-sm prose-invert max-w-none text-white/90"
178
+ dangerouslySetInnerHTML={{ __html: formatReleaseNotes(update.releaseNotes) }}
179
+ />
180
+ </div>
181
+ )}
182
+
183
+ {/* Actions */}
184
+ <div className="mt-4 flex gap-2">
185
+ <button
186
+ onClick={handleDownload}
187
+ className="flex-1 bg-white text-blue-600 px-4 py-2 rounded-lg font-medium text-sm hover:bg-blue-50 transition-colors"
188
+ >
189
+ Download Now
190
+ </button>
191
+ <button
192
+ onClick={handleDismiss}
193
+ className="px-4 py-2 text-white/70 hover:text-white text-sm transition-colors"
194
+ >
195
+ Later
196
+ </button>
197
+ </div>
198
+ </div>
199
+ )}
200
+
201
+ {/* Downloading State */}
202
+ {update.status === 'downloading' && (
203
+ <div className="bg-gradient-to-br from-slate-800 to-slate-900 text-white p-4 rounded-xl shadow-2xl border border-slate-700/50">
204
+ <div className="flex items-center gap-3 mb-3">
205
+ <div className="w-8 h-8 bg-blue-500/20 rounded-lg flex items-center justify-center">
206
+ <svg
207
+ className="w-5 h-5 text-blue-400 animate-pulse"
208
+ fill="none"
209
+ stroke="currentColor"
210
+ viewBox="0 0 24 24"
211
+ >
212
+ <path
213
+ strokeLinecap="round"
214
+ strokeLinejoin="round"
215
+ strokeWidth={2}
216
+ d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
217
+ />
218
+ </svg>
219
+ </div>
220
+ <div>
221
+ <h4 className="font-semibold text-sm">Downloading Update...</h4>
222
+ <p className="text-xs text-slate-400">Please wait</p>
223
+ </div>
224
+ </div>
225
+
226
+ {/* Progress Bar */}
227
+ <div className="h-2 bg-slate-700 rounded-full overflow-hidden">
228
+ <div
229
+ className="h-full bg-gradient-to-r from-blue-500 to-cyan-400 transition-all duration-300 ease-out"
230
+ style={{ width: `${update.percent || 0}%` }}
231
+ />
232
+ </div>
233
+ <p className="text-xs text-slate-400 mt-2 text-right">
234
+ {Math.round(update.percent || 0)}% complete
235
+ </p>
236
+ </div>
237
+ )}
238
+
239
+ {/* Ready to Install State */}
240
+ {update.status === 'ready' && (
241
+ <div className="bg-gradient-to-br from-green-600 to-emerald-700 text-white p-4 rounded-xl shadow-2xl border border-green-500/30">
242
+ <div className="flex items-center gap-3 mb-3">
243
+ <div className="w-8 h-8 bg-white/20 rounded-lg flex items-center justify-center">
244
+ <svg
245
+ className="w-5 h-5"
246
+ fill="none"
247
+ stroke="currentColor"
248
+ viewBox="0 0 24 24"
249
+ >
250
+ <path
251
+ strokeLinecap="round"
252
+ strokeLinejoin="round"
253
+ strokeWidth={2}
254
+ d="M5 13l4 4L19 7"
255
+ />
256
+ </svg>
257
+ </div>
258
+ <div>
259
+ <h4 className="font-semibold text-sm">Update Ready!</h4>
260
+ <p className="text-xs text-white/80">Version {update.version} is ready to install</p>
261
+ </div>
262
+ </div>
263
+
264
+ <p className="text-sm text-white/90 mb-4">
265
+ Restart markupr to apply the update. Your work will be saved.
266
+ </p>
267
+
268
+ <button
269
+ onClick={handleInstall}
270
+ className="w-full bg-white text-green-600 px-4 py-2.5 rounded-lg font-medium text-sm hover:bg-green-50 transition-colors flex items-center justify-center gap-2"
271
+ >
272
+ <svg
273
+ className="w-4 h-4"
274
+ fill="none"
275
+ stroke="currentColor"
276
+ viewBox="0 0 24 24"
277
+ >
278
+ <path
279
+ strokeLinecap="round"
280
+ strokeLinejoin="round"
281
+ strokeWidth={2}
282
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
283
+ />
284
+ </svg>
285
+ Restart Now
286
+ </button>
287
+ </div>
288
+ )}
289
+
290
+ {/* Error State */}
291
+ {update.status === 'error' && update.message && (
292
+ <div className="bg-gradient-to-br from-red-600 to-red-700 text-white p-4 rounded-xl shadow-2xl border border-red-500/30">
293
+ <div className="flex items-center gap-3 mb-2">
294
+ <div className="w-8 h-8 bg-white/20 rounded-lg flex items-center justify-center">
295
+ <svg
296
+ className="w-5 h-5"
297
+ fill="none"
298
+ stroke="currentColor"
299
+ viewBox="0 0 24 24"
300
+ >
301
+ <path
302
+ strokeLinecap="round"
303
+ strokeLinejoin="round"
304
+ strokeWidth={2}
305
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
306
+ />
307
+ </svg>
308
+ </div>
309
+ <div>
310
+ <h4 className="font-semibold text-sm">Update Failed</h4>
311
+ <p className="text-xs text-white/80">{update.message}</p>
312
+ </div>
313
+ </div>
314
+
315
+ <div className="flex gap-2 mt-3">
316
+ <button
317
+ onClick={() => window.markupr.updates.check()}
318
+ className="flex-1 bg-white/20 hover:bg-white/30 px-4 py-2 rounded-lg font-medium text-sm transition-colors"
319
+ >
320
+ Try Again
321
+ </button>
322
+ <button
323
+ onClick={handleDismiss}
324
+ className="px-4 py-2 text-white/70 hover:text-white text-sm transition-colors"
325
+ >
326
+ Dismiss
327
+ </button>
328
+ </div>
329
+ </div>
330
+ )}
331
+ </div>
332
+ );
333
+ }
334
+
335
+ // =============================================================================
336
+ // Helper Functions
337
+ // =============================================================================
338
+
339
+ /**
340
+ * Format release notes for display
341
+ * Handles markdown-like formatting and converts to safe HTML
342
+ */
343
+ function formatReleaseNotes(notes: string): string {
344
+ // Simple markdown-like formatting
345
+ return notes
346
+ // Escape HTML
347
+ .replace(/&/g, '&amp;')
348
+ .replace(/</g, '&lt;')
349
+ .replace(/>/g, '&gt;')
350
+ // Convert markdown headers
351
+ .replace(/^### (.+)$/gm, '<h3 class="font-semibold mt-2">$1</h3>')
352
+ .replace(/^## (.+)$/gm, '<h2 class="font-semibold mt-2 text-base">$1</h2>')
353
+ .replace(/^# (.+)$/gm, '<h1 class="font-bold mt-2 text-lg">$1</h1>')
354
+ // Convert bold
355
+ .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
356
+ // Convert italic
357
+ .replace(/\*(.+?)\*/g, '<em>$1</em>')
358
+ // Convert bullet points
359
+ .replace(/^[-*] (.+)$/gm, '<li class="ml-4">$1</li>')
360
+ // Convert newlines to breaks
361
+ .replace(/\n/g, '<br />');
362
+ }
363
+
364
+ // =============================================================================
365
+ // CSS Animation (add to your global styles or Tailwind config)
366
+ // =============================================================================
367
+
368
+ // Add this to your CSS or tailwind.config.js:
369
+ // .animate-slide-up {
370
+ // animation: slideUp 0.3s ease-out;
371
+ // }
372
+ // @keyframes slideUp {
373
+ // from { opacity: 0; transform: translateY(20px); }
374
+ // to { opacity: 1; transform: translateY(0); }
375
+ // }
376
+
377
+ export default UpdateNotification;