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,553 @@
1
+ /**
2
+ * HTML Template for Standalone Export
3
+ *
4
+ * Generates a self-contained HTML document with:
5
+ * - Embedded CSS (no external dependencies)
6
+ * - Dark theme matching markupr aesthetic
7
+ * - Base64-embedded images
8
+ * - Responsive design
9
+ * - Print-friendly styles
10
+ */
11
+
12
+ import type { Session, FeedbackItem, FeedbackCategory, FeedbackSeverity } from '../MarkdownGenerator';
13
+
14
+ // ============================================================================
15
+ // Types
16
+ // ============================================================================
17
+
18
+ export interface HtmlExportOptions {
19
+ projectName?: string;
20
+ includeImages?: boolean;
21
+ theme?: 'dark' | 'light';
22
+ }
23
+
24
+ // ============================================================================
25
+ // Helper Functions
26
+ // ============================================================================
27
+
28
+ function formatDuration(ms: number): string {
29
+ const totalSeconds = Math.floor(ms / 1000);
30
+ const mins = Math.floor(totalSeconds / 60);
31
+ const secs = totalSeconds % 60;
32
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
33
+ }
34
+
35
+ function formatTimestamp(ms: number): string {
36
+ return new Date(ms).toLocaleString('en-US', {
37
+ year: 'numeric',
38
+ month: 'short',
39
+ day: 'numeric',
40
+ hour: '2-digit',
41
+ minute: '2-digit',
42
+ });
43
+ }
44
+
45
+ function generateTitle(transcription: string): string {
46
+ const firstSentence = transcription.split(/[.!?]/)[0].trim();
47
+ if (firstSentence.length <= 50) return escapeHtml(firstSentence);
48
+ return escapeHtml(firstSentence.slice(0, 47) + '...');
49
+ }
50
+
51
+ function escapeHtml(text: string): string {
52
+ const map: Record<string, string> = {
53
+ '&': '&amp;',
54
+ '<': '&lt;',
55
+ '>': '&gt;',
56
+ '"': '&quot;',
57
+ "'": '&#039;',
58
+ };
59
+ return text.replace(/[&<>"']/g, (char) => map[char] || char);
60
+ }
61
+
62
+ function countByCategory(session: Session): Record<string, number> {
63
+ return session.feedbackItems.reduce((acc, item) => {
64
+ const category = item.category || 'General';
65
+ acc[category] = (acc[category] || 0) + 1;
66
+ return acc;
67
+ }, {} as Record<string, number>);
68
+ }
69
+
70
+ function getCategoryClass(category: FeedbackCategory): string {
71
+ const classes: Record<FeedbackCategory, string> = {
72
+ Bug: 'tag-bug',
73
+ 'UX Issue': 'tag-ux',
74
+ Suggestion: 'tag-suggestion',
75
+ Performance: 'tag-performance',
76
+ Question: 'tag-question',
77
+ General: 'tag-general',
78
+ };
79
+ return classes[category] || 'tag-general';
80
+ }
81
+
82
+ function getSeverityClass(severity: FeedbackSeverity): string {
83
+ const classes: Record<FeedbackSeverity, string> = {
84
+ Critical: 'severity-critical',
85
+ High: 'severity-high',
86
+ Medium: 'severity-medium',
87
+ Low: 'severity-low',
88
+ };
89
+ return classes[severity] || 'severity-medium';
90
+ }
91
+
92
+ // ============================================================================
93
+ // CSS Styles
94
+ // ============================================================================
95
+
96
+ function getStyles(theme: 'dark' | 'light'): string {
97
+ const isDark = theme === 'dark';
98
+
99
+ return `
100
+ :root {
101
+ --bg: ${isDark ? '#0f172a' : '#ffffff'};
102
+ --bg-secondary: ${isDark ? '#1e293b' : '#f8fafc'};
103
+ --text: ${isDark ? '#e2e8f0' : '#1e293b'};
104
+ --text-secondary: ${isDark ? '#94a3b8' : '#64748b'};
105
+ --border: ${isDark ? '#334155' : '#e2e8f0'};
106
+ --accent: #3b82f6;
107
+ --accent-soft: ${isDark ? 'rgba(59, 130, 246, 0.15)' : 'rgba(59, 130, 246, 0.1)'};
108
+ }
109
+
110
+ * {
111
+ box-sizing: border-box;
112
+ margin: 0;
113
+ padding: 0;
114
+ }
115
+
116
+ body {
117
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
118
+ background: var(--bg);
119
+ color: var(--text);
120
+ line-height: 1.6;
121
+ -webkit-font-smoothing: antialiased;
122
+ -moz-osx-font-smoothing: grayscale;
123
+ }
124
+
125
+ .container {
126
+ max-width: 900px;
127
+ margin: 0 auto;
128
+ padding: 2rem;
129
+ }
130
+
131
+ /* Header */
132
+ header {
133
+ margin-bottom: 2.5rem;
134
+ padding-bottom: 1.5rem;
135
+ border-bottom: 2px solid var(--accent);
136
+ }
137
+
138
+ h1 {
139
+ font-size: 2rem;
140
+ font-weight: 700;
141
+ color: ${isDark ? '#ffffff' : '#0f172a'};
142
+ margin-bottom: 0.75rem;
143
+ letter-spacing: -0.025em;
144
+ }
145
+
146
+ .meta {
147
+ color: var(--text-secondary);
148
+ font-size: 0.875rem;
149
+ display: flex;
150
+ gap: 1rem;
151
+ flex-wrap: wrap;
152
+ }
153
+
154
+ .meta-item {
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 0.375rem;
158
+ }
159
+
160
+ .meta-item svg {
161
+ width: 14px;
162
+ height: 14px;
163
+ opacity: 0.7;
164
+ }
165
+
166
+ /* Feedback Items */
167
+ .feedback-list {
168
+ display: flex;
169
+ flex-direction: column;
170
+ gap: 1.5rem;
171
+ }
172
+
173
+ .feedback-item {
174
+ background: var(--bg-secondary);
175
+ border: 1px solid var(--border);
176
+ border-radius: 12px;
177
+ padding: 1.5rem;
178
+ transition: box-shadow 0.2s ease;
179
+ }
180
+
181
+ .feedback-item:hover {
182
+ box-shadow: 0 4px 12px ${isDark ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.08)'};
183
+ }
184
+
185
+ .feedback-header {
186
+ display: flex;
187
+ align-items: flex-start;
188
+ justify-content: space-between;
189
+ gap: 1rem;
190
+ margin-bottom: 1rem;
191
+ }
192
+
193
+ .feedback-title {
194
+ font-size: 1.125rem;
195
+ font-weight: 600;
196
+ color: ${isDark ? '#ffffff' : '#0f172a'};
197
+ margin: 0;
198
+ }
199
+
200
+ .feedback-id {
201
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', monospace;
202
+ font-size: 0.75rem;
203
+ font-weight: 500;
204
+ color: var(--text-secondary);
205
+ background: var(--bg);
206
+ padding: 0.25rem 0.5rem;
207
+ border-radius: 4px;
208
+ white-space: nowrap;
209
+ }
210
+
211
+ .tags {
212
+ display: flex;
213
+ gap: 0.5rem;
214
+ flex-wrap: wrap;
215
+ margin-bottom: 1rem;
216
+ }
217
+
218
+ .tag {
219
+ padding: 0.25rem 0.75rem;
220
+ border-radius: 9999px;
221
+ font-size: 0.75rem;
222
+ font-weight: 600;
223
+ text-transform: uppercase;
224
+ letter-spacing: 0.025em;
225
+ }
226
+
227
+ .tag-bug { background: rgba(239, 68, 68, 0.15); color: #ef4444; }
228
+ .tag-ux { background: rgba(245, 158, 11, 0.15); color: #f59e0b; }
229
+ .tag-suggestion { background: rgba(59, 130, 246, 0.15); color: #3b82f6; }
230
+ .tag-performance { background: rgba(34, 197, 94, 0.18); color: #22c55e; }
231
+ .tag-question { background: rgba(139, 92, 246, 0.15); color: #8b5cf6; }
232
+ .tag-general { background: rgba(107, 114, 128, 0.15); color: #6b7280; }
233
+
234
+ .severity-critical { background: rgba(220, 38, 38, 0.15); color: #dc2626; }
235
+ .severity-high { background: rgba(234, 88, 12, 0.15); color: #ea580c; }
236
+ .severity-medium { background: rgba(202, 138, 4, 0.15); color: #ca8a04; }
237
+ .severity-low { background: rgba(101, 163, 13, 0.15); color: #65a30d; }
238
+
239
+ blockquote {
240
+ border-left: 3px solid var(--accent);
241
+ margin: 0;
242
+ padding: 0.75rem 0 0.75rem 1rem;
243
+ color: var(--text);
244
+ font-size: 0.9375rem;
245
+ background: var(--accent-soft);
246
+ border-radius: 0 8px 8px 0;
247
+ }
248
+
249
+ .screenshots {
250
+ display: grid;
251
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
252
+ gap: 0.75rem;
253
+ margin-top: 1rem;
254
+ }
255
+
256
+ .screenshot {
257
+ border-radius: 8px;
258
+ overflow: hidden;
259
+ border: 1px solid var(--border);
260
+ background: var(--bg);
261
+ }
262
+
263
+ .screenshot img {
264
+ width: 100%;
265
+ height: auto;
266
+ display: block;
267
+ }
268
+
269
+ /* Summary Section */
270
+ .summary {
271
+ margin-top: 3rem;
272
+ padding-top: 2rem;
273
+ border-top: 1px solid var(--border);
274
+ }
275
+
276
+ .summary h2 {
277
+ font-size: 1.25rem;
278
+ font-weight: 600;
279
+ color: ${isDark ? '#ffffff' : '#0f172a'};
280
+ margin-bottom: 1.25rem;
281
+ }
282
+
283
+ .summary-table {
284
+ width: 100%;
285
+ border-collapse: collapse;
286
+ font-size: 0.875rem;
287
+ }
288
+
289
+ .summary-table th,
290
+ .summary-table td {
291
+ padding: 0.75rem 1rem;
292
+ text-align: left;
293
+ border-bottom: 1px solid var(--border);
294
+ }
295
+
296
+ .summary-table th {
297
+ font-weight: 600;
298
+ color: var(--text-secondary);
299
+ text-transform: uppercase;
300
+ font-size: 0.75rem;
301
+ letter-spacing: 0.05em;
302
+ }
303
+
304
+ .summary-table tbody tr:hover {
305
+ background: var(--accent-soft);
306
+ }
307
+
308
+ .summary-table tfoot td {
309
+ font-weight: 600;
310
+ border-top: 2px solid var(--border);
311
+ background: var(--bg-secondary);
312
+ }
313
+
314
+ /* Footer */
315
+ footer {
316
+ margin-top: 3rem;
317
+ padding-top: 1.5rem;
318
+ border-top: 1px solid var(--border);
319
+ text-align: center;
320
+ color: var(--text-secondary);
321
+ font-size: 0.8125rem;
322
+ }
323
+
324
+ footer a {
325
+ color: var(--accent);
326
+ text-decoration: none;
327
+ }
328
+
329
+ footer a:hover {
330
+ text-decoration: underline;
331
+ }
332
+
333
+ /* Print Styles */
334
+ @media print {
335
+ body {
336
+ background: white;
337
+ color: black;
338
+ }
339
+
340
+ .container {
341
+ max-width: 100%;
342
+ padding: 1rem;
343
+ }
344
+
345
+ .feedback-item {
346
+ break-inside: avoid;
347
+ page-break-inside: avoid;
348
+ }
349
+
350
+ .screenshot img {
351
+ max-height: 300px;
352
+ object-fit: contain;
353
+ }
354
+ }
355
+
356
+ /* Responsive */
357
+ @media (max-width: 640px) {
358
+ .container {
359
+ padding: 1rem;
360
+ }
361
+
362
+ h1 {
363
+ font-size: 1.5rem;
364
+ }
365
+
366
+ .feedback-header {
367
+ flex-direction: column;
368
+ align-items: flex-start;
369
+ }
370
+
371
+ .feedback-id {
372
+ align-self: flex-start;
373
+ }
374
+
375
+ .screenshots {
376
+ grid-template-columns: 1fr;
377
+ }
378
+ }
379
+ `;
380
+ }
381
+
382
+ // ============================================================================
383
+ // HTML Generator
384
+ // ============================================================================
385
+
386
+ function generateFeedbackItemHtml(item: FeedbackItem, index: number, includeImages: boolean): string {
387
+ const id = `FB-${(index + 1).toString().padStart(3, '0')}`;
388
+ const category = item.category || 'General';
389
+ const severity = item.severity || 'Medium';
390
+
391
+ let screenshotsHtml = '';
392
+ if (includeImages && item.screenshots.length > 0) {
393
+ const imagesHtml = item.screenshots
394
+ .map((ss, ssIndex) => {
395
+ if (ss.base64) {
396
+ return `
397
+ <div class="screenshot">
398
+ <img src="data:image/png;base64,${ss.base64}" alt="Screenshot ${ssIndex + 1}" loading="lazy" />
399
+ </div>
400
+ `;
401
+ }
402
+ return '';
403
+ })
404
+ .filter(Boolean)
405
+ .join('');
406
+
407
+ if (imagesHtml) {
408
+ screenshotsHtml = `<div class="screenshots">${imagesHtml}</div>`;
409
+ }
410
+ }
411
+
412
+ return `
413
+ <article class="feedback-item">
414
+ <div class="feedback-header">
415
+ <h3 class="feedback-title">${generateTitle(item.transcription)}</h3>
416
+ <span class="feedback-id">${id}</span>
417
+ </div>
418
+ <div class="tags">
419
+ <span class="tag ${getCategoryClass(category as FeedbackCategory)}">${escapeHtml(category)}</span>
420
+ <span class="tag ${getSeverityClass(severity as FeedbackSeverity)}">${escapeHtml(severity)}</span>
421
+ </div>
422
+ <blockquote>${escapeHtml(item.transcription)}</blockquote>
423
+ ${screenshotsHtml}
424
+ </article>
425
+ `;
426
+ }
427
+
428
+ function generateSummaryTableHtml(session: Session): string {
429
+ const categories = countByCategory(session);
430
+ const rows = Object.entries(categories)
431
+ .map(([category, count]) => `
432
+ <tr>
433
+ <td>${escapeHtml(category)}</td>
434
+ <td>${count}</td>
435
+ </tr>
436
+ `)
437
+ .join('');
438
+
439
+ return `
440
+ <table class="summary-table">
441
+ <thead>
442
+ <tr>
443
+ <th>Category</th>
444
+ <th>Count</th>
445
+ </tr>
446
+ </thead>
447
+ <tbody>
448
+ ${rows}
449
+ </tbody>
450
+ <tfoot>
451
+ <tr>
452
+ <td>Total</td>
453
+ <td>${session.feedbackItems.length}</td>
454
+ </tr>
455
+ </tfoot>
456
+ </table>
457
+ `;
458
+ }
459
+
460
+ // ============================================================================
461
+ // Main Export Function
462
+ // ============================================================================
463
+
464
+ export function generateHtmlDocument(session: Session, options: HtmlExportOptions = {}): string {
465
+ const {
466
+ projectName = session.metadata?.sourceName || 'Project',
467
+ includeImages = true,
468
+ theme = 'dark',
469
+ } = options;
470
+
471
+ const duration = session.endTime
472
+ ? formatDuration(session.endTime - session.startTime)
473
+ : 'In Progress';
474
+ const timestamp = formatTimestamp(session.endTime || Date.now());
475
+ const screenshotCount = session.feedbackItems.reduce(
476
+ (sum, item) => sum + item.screenshots.length,
477
+ 0
478
+ );
479
+
480
+ const feedbackItemsHtml = session.feedbackItems
481
+ .map((item, index) => generateFeedbackItemHtml(item, index, includeImages))
482
+ .join('');
483
+
484
+ return `<!DOCTYPE html>
485
+ <html lang="en">
486
+ <head>
487
+ <meta charset="UTF-8">
488
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
489
+ <meta name="generator" content="markupr">
490
+ <meta name="theme-color" content="${theme === 'dark' ? '#0f172a' : '#ffffff'}">
491
+ <title>${escapeHtml(projectName)} - Feedback Report</title>
492
+ <style>${getStyles(theme)}</style>
493
+ </head>
494
+ <body>
495
+ <div class="container">
496
+ <header>
497
+ <h1>${escapeHtml(projectName)} Feedback Report</h1>
498
+ <div class="meta">
499
+ <span class="meta-item">
500
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
501
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
502
+ <line x1="16" y1="2" x2="16" y2="6"></line>
503
+ <line x1="8" y1="2" x2="8" y2="6"></line>
504
+ <line x1="3" y1="10" x2="21" y2="10"></line>
505
+ </svg>
506
+ ${timestamp}
507
+ </span>
508
+ <span class="meta-item">
509
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
510
+ <circle cx="12" cy="12" r="10"></circle>
511
+ <polyline points="12 6 12 12 16 14"></polyline>
512
+ </svg>
513
+ ${duration}
514
+ </span>
515
+ <span class="meta-item">
516
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
517
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
518
+ <polyline points="14 2 14 8 20 8"></polyline>
519
+ <line x1="16" y1="13" x2="8" y2="13"></line>
520
+ <line x1="16" y1="17" x2="8" y2="17"></line>
521
+ </svg>
522
+ ${session.feedbackItems.length} items
523
+ </span>
524
+ <span class="meta-item">
525
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
526
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
527
+ <circle cx="8.5" cy="8.5" r="1.5"></circle>
528
+ <polyline points="21 15 16 10 5 21"></polyline>
529
+ </svg>
530
+ ${screenshotCount} screenshots
531
+ </span>
532
+ </div>
533
+ </header>
534
+
535
+ <main>
536
+ <div class="feedback-list">
537
+ ${feedbackItemsHtml}
538
+ </div>
539
+
540
+ <section class="summary">
541
+ <h2>Summary</h2>
542
+ ${generateSummaryTableHtml(session)}
543
+ </section>
544
+ </main>
545
+
546
+ <footer>
547
+ Generated by <a href="https://markupr.com" target="_blank" rel="noopener">markupr</a> ·
548
+ <a href="https://ko-fi.com/eddiesanjuan" target="_blank" rel="noopener">Support development</a>
549
+ </footer>
550
+ </div>
551
+ </body>
552
+ </html>`;
553
+ }