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,656 @@
1
+ /**
2
+ * ClarificationQuestions - Smart Feedback Context Collector
3
+ *
4
+ * A modal interface for gathering additional context on ambiguous feedback.
5
+ * Helps users clarify vague references, unclear locations, and missing details.
6
+ *
7
+ * Features:
8
+ * - Progress indicator showing question number
9
+ * - Skippable questions (users aren't forced to answer)
10
+ * - Skip all option for users in a hurry
11
+ * - Smooth animations between questions
12
+ * - Keyboard navigation (Enter to continue, Escape to skip)
13
+ */
14
+
15
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
16
+
17
+ // =============================================================================
18
+ // Types
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Type of clarification needed (mirrored from main process)
23
+ */
24
+ export type ClarificationType =
25
+ | 'location'
26
+ | 'reproduction'
27
+ | 'expectation'
28
+ | 'frequency'
29
+ | 'reference'
30
+ | 'comparison';
31
+
32
+ /**
33
+ * A single clarification question
34
+ */
35
+ export interface ClarificationQuestion {
36
+ id: string;
37
+ feedbackItemId: string;
38
+ question: string;
39
+ type: ClarificationType;
40
+ placeholder: string;
41
+ matchedText?: string;
42
+ }
43
+
44
+ /**
45
+ * Props for ClarificationQuestions component
46
+ */
47
+ export interface ClarificationQuestionsProps {
48
+ /** Array of questions to ask */
49
+ questions: ClarificationQuestion[];
50
+ /** Called when all questions are answered (or skipped) */
51
+ onComplete: (answers: Record<string, string>) => void;
52
+ /** Called when user skips all remaining questions */
53
+ onSkipAll: () => void;
54
+ /** Optional: Custom title for the modal */
55
+ title?: string;
56
+ }
57
+
58
+ // =============================================================================
59
+ // Helper Components
60
+ // =============================================================================
61
+
62
+ /**
63
+ * Icon component for question types
64
+ */
65
+ const QuestionTypeIcon: React.FC<{ type: ClarificationType }> = ({ type }) => {
66
+ const iconProps = {
67
+ width: 20,
68
+ height: 20,
69
+ viewBox: '0 0 24 24',
70
+ fill: 'none',
71
+ stroke: 'currentColor',
72
+ strokeWidth: 2,
73
+ strokeLinecap: 'round' as const,
74
+ strokeLinejoin: 'round' as const,
75
+ };
76
+
77
+ switch (type) {
78
+ case 'location':
79
+ return (
80
+ <svg {...iconProps}>
81
+ <path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z" />
82
+ <circle cx="12" cy="10" r="3" />
83
+ </svg>
84
+ );
85
+ case 'reproduction':
86
+ return (
87
+ <svg {...iconProps}>
88
+ <circle cx="12" cy="12" r="10" />
89
+ <polyline points="12 6 12 12 16 14" />
90
+ </svg>
91
+ );
92
+ case 'expectation':
93
+ return (
94
+ <svg {...iconProps}>
95
+ <path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
96
+ </svg>
97
+ );
98
+ case 'frequency':
99
+ return (
100
+ <svg {...iconProps}>
101
+ <path d="M22 12h-4l-3 9L9 3l-3 9H2" />
102
+ </svg>
103
+ );
104
+ case 'reference':
105
+ return (
106
+ <svg {...iconProps}>
107
+ <path d="M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5" />
108
+ </svg>
109
+ );
110
+ case 'comparison':
111
+ return (
112
+ <svg {...iconProps}>
113
+ <line x1="18" y1="20" x2="18" y2="10" />
114
+ <line x1="12" y1="20" x2="12" y2="4" />
115
+ <line x1="6" y1="20" x2="6" y2="14" />
116
+ </svg>
117
+ );
118
+ default:
119
+ return (
120
+ <svg {...iconProps}>
121
+ <circle cx="12" cy="12" r="10" />
122
+ <path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
123
+ <line x1="12" y1="17" x2="12.01" y2="17" />
124
+ </svg>
125
+ );
126
+ }
127
+ };
128
+
129
+ /**
130
+ * Human-readable label for question type
131
+ */
132
+ const getTypeLabel = (type: ClarificationType): string => {
133
+ const labels: Record<ClarificationType, string> = {
134
+ location: 'Location',
135
+ reproduction: 'Steps',
136
+ expectation: 'Expected Behavior',
137
+ frequency: 'Frequency',
138
+ reference: 'Reference',
139
+ comparison: 'Comparison',
140
+ };
141
+ return labels[type] || 'Context';
142
+ };
143
+
144
+ // =============================================================================
145
+ // Main Component
146
+ // =============================================================================
147
+
148
+ export function ClarificationQuestions({
149
+ questions,
150
+ onComplete,
151
+ onSkipAll,
152
+ title = 'Quick Clarification',
153
+ }: ClarificationQuestionsProps) {
154
+ const [answers, setAnswers] = useState<Record<string, string>>({});
155
+ const [currentIndex, setCurrentIndex] = useState(0);
156
+ const [isExiting, setIsExiting] = useState(false);
157
+ const [direction, setDirection] = useState<'forward' | 'backward'>('forward');
158
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
159
+
160
+ const currentQuestion = questions[currentIndex];
161
+ const isLast = currentIndex === questions.length - 1;
162
+ const answeredCount = Object.values(answers).filter((a) => a.trim()).length;
163
+
164
+ // Focus textarea when question changes
165
+ useEffect(() => {
166
+ if (textareaRef.current) {
167
+ textareaRef.current.focus();
168
+ }
169
+ }, [currentIndex]);
170
+
171
+ // Handle keyboard navigation
172
+ useEffect(() => {
173
+ const handleKeyDown = (e: KeyboardEvent) => {
174
+ if (e.key === 'Escape') {
175
+ e.preventDefault();
176
+ handleSkip();
177
+ } else if (e.key === 'Enter' && e.metaKey) {
178
+ e.preventDefault();
179
+ handleNext();
180
+ }
181
+ };
182
+
183
+ window.addEventListener('keydown', handleKeyDown);
184
+ return () => window.removeEventListener('keydown', handleKeyDown);
185
+ // eslint-disable-next-line react-hooks/exhaustive-deps
186
+ }, [currentIndex, isLast]);
187
+
188
+ const handleAnswer = useCallback((answer: string) => {
189
+ setAnswers((prev) => ({ ...prev, [currentQuestion.id]: answer }));
190
+ }, [currentQuestion?.id]);
191
+
192
+ const handleNext = useCallback(() => {
193
+ if (isLast) {
194
+ setIsExiting(true);
195
+ setTimeout(() => {
196
+ onComplete(answers);
197
+ }, 200);
198
+ } else {
199
+ setDirection('forward');
200
+ setCurrentIndex((i) => i + 1);
201
+ }
202
+ }, [isLast, answers, onComplete]);
203
+
204
+ const handleSkip = useCallback(() => {
205
+ if (isLast) {
206
+ setIsExiting(true);
207
+ setTimeout(() => {
208
+ onComplete(answers);
209
+ }, 200);
210
+ } else {
211
+ setDirection('forward');
212
+ setCurrentIndex((i) => i + 1);
213
+ }
214
+ }, [isLast, answers, onComplete]);
215
+
216
+ const handleBack = useCallback(() => {
217
+ if (currentIndex > 0) {
218
+ setDirection('backward');
219
+ setCurrentIndex((i) => i - 1);
220
+ }
221
+ }, [currentIndex]);
222
+
223
+ const handleSkipAll = useCallback(() => {
224
+ setIsExiting(true);
225
+ setTimeout(() => {
226
+ onSkipAll();
227
+ }, 200);
228
+ }, [onSkipAll]);
229
+
230
+ // Don't render if no questions
231
+ if (questions.length === 0) {
232
+ return null;
233
+ }
234
+
235
+ return (
236
+ <div
237
+ style={{
238
+ ...styles.overlay,
239
+ opacity: isExiting ? 0 : 1,
240
+ }}
241
+ >
242
+ <div
243
+ style={{
244
+ ...styles.modal,
245
+ transform: isExiting ? 'scale(0.95) translateY(10px)' : 'scale(1) translateY(0)',
246
+ }}
247
+ >
248
+ {/* Header */}
249
+ <div style={styles.header}>
250
+ <div style={styles.headerLeft}>
251
+ <div style={styles.iconWrapper}>
252
+ <QuestionTypeIcon type={currentQuestion.type} />
253
+ </div>
254
+ <div>
255
+ <h2 style={styles.title}>{title}</h2>
256
+ <span style={styles.typeLabel}>
257
+ {getTypeLabel(currentQuestion.type)}
258
+ </span>
259
+ </div>
260
+ </div>
261
+ <button onClick={handleSkipAll} style={styles.skipAllButton}>
262
+ Skip all
263
+ </button>
264
+ </div>
265
+
266
+ {/* Progress */}
267
+ <div style={styles.progressContainer}>
268
+ <div style={styles.progressBar}>
269
+ <div
270
+ style={{
271
+ ...styles.progressFill,
272
+ width: `${((currentIndex + 1) / questions.length) * 100}%`,
273
+ }}
274
+ />
275
+ </div>
276
+ <span style={styles.progressText}>
277
+ {currentIndex + 1} of {questions.length}
278
+ </span>
279
+ </div>
280
+
281
+ {/* Question */}
282
+ <div
283
+ style={{
284
+ ...styles.questionContainer,
285
+ animation: `${direction === 'forward' ? 'pageSlideInRight' : 'pageSlideInLeft'} 0.2s ease-out`,
286
+ }}
287
+ key={currentQuestion.id}
288
+ >
289
+ <h3 style={styles.question}>{currentQuestion.question}</h3>
290
+ <p style={styles.hint}>
291
+ This will help provide more actionable context to your feedback.
292
+ </p>
293
+
294
+ {/* Matched text indicator */}
295
+ {currentQuestion.matchedText && (
296
+ <div style={styles.matchedText}>
297
+ Triggered by:{' '}
298
+ <code style={styles.matchedCode}>
299
+ &quot;{currentQuestion.matchedText}&quot;
300
+ </code>
301
+ </div>
302
+ )}
303
+ </div>
304
+
305
+ {/* Input */}
306
+ <textarea
307
+ ref={textareaRef}
308
+ value={answers[currentQuestion.id] || ''}
309
+ onChange={(e) => handleAnswer(e.target.value)}
310
+ placeholder={currentQuestion.placeholder}
311
+ style={styles.textarea}
312
+ rows={3}
313
+ />
314
+
315
+ {/* Actions */}
316
+ <div style={styles.actions}>
317
+ <div style={styles.actionsLeft}>
318
+ {currentIndex > 0 && (
319
+ <button onClick={handleBack} style={styles.backButton}>
320
+ <svg
321
+ width="16"
322
+ height="16"
323
+ viewBox="0 0 24 24"
324
+ fill="none"
325
+ stroke="currentColor"
326
+ strokeWidth="2"
327
+ >
328
+ <polyline points="15 18 9 12 15 6" />
329
+ </svg>
330
+ Back
331
+ </button>
332
+ )}
333
+ </div>
334
+
335
+ <div style={styles.actionsRight}>
336
+ <button onClick={handleSkip} style={styles.skipButton}>
337
+ Skip
338
+ </button>
339
+ <button onClick={handleNext} style={styles.nextButton}>
340
+ {isLast ? 'Done' : 'Next'}
341
+ {!isLast && (
342
+ <svg
343
+ width="16"
344
+ height="16"
345
+ viewBox="0 0 24 24"
346
+ fill="none"
347
+ stroke="currentColor"
348
+ strokeWidth="2"
349
+ >
350
+ <polyline points="9 18 15 12 9 6" />
351
+ </svg>
352
+ )}
353
+ </button>
354
+ </div>
355
+ </div>
356
+
357
+ {/* Keyboard hints */}
358
+ <div style={styles.keyboardHints}>
359
+ <span style={styles.keyboardHint}>
360
+ <kbd style={styles.kbd}>Esc</kbd> Skip
361
+ </span>
362
+ <span style={styles.keyboardHint}>
363
+ <kbd style={styles.kbd}>Cmd</kbd>+<kbd style={styles.kbd}>Enter</kbd> Continue
364
+ </span>
365
+ </div>
366
+
367
+ {/* Answered count indicator */}
368
+ {answeredCount > 0 && (
369
+ <div style={styles.answeredIndicator}>
370
+ {answeredCount} answered
371
+ </div>
372
+ )}
373
+ </div>
374
+
375
+ {/* pageSlideInRight, pageSlideInLeft, pageFadeIn, dialogEnter keyframes provided by animations.css */}
376
+ </div>
377
+ );
378
+ }
379
+
380
+ // =============================================================================
381
+ // Styles
382
+ // =============================================================================
383
+
384
+ const styles: Record<string, React.CSSProperties> = {
385
+ overlay: {
386
+ position: 'fixed',
387
+ inset: 0,
388
+ backgroundColor: 'rgba(0, 0, 0, 0.6)',
389
+ backdropFilter: 'blur(8px)',
390
+ display: 'flex',
391
+ alignItems: 'center',
392
+ justifyContent: 'center',
393
+ zIndex: 50,
394
+ padding: 16,
395
+ transition: 'opacity 0.2s ease-out',
396
+ animation: 'pageFadeIn 0.2s ease-out',
397
+ },
398
+
399
+ modal: {
400
+ backgroundColor: 'var(--bg-secondary)',
401
+ border: '1px solid rgba(51, 65, 85, 0.8)',
402
+ borderRadius: 16,
403
+ padding: 24,
404
+ maxWidth: 480,
405
+ width: '100%',
406
+ boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05)',
407
+ transition: 'transform 0.2s ease-out',
408
+ animation: 'dialogEnter 0.3s ease-out',
409
+ position: 'relative',
410
+ },
411
+
412
+ header: {
413
+ display: 'flex',
414
+ alignItems: 'flex-start',
415
+ justifyContent: 'space-between',
416
+ marginBottom: 20,
417
+ },
418
+
419
+ headerLeft: {
420
+ display: 'flex',
421
+ alignItems: 'center',
422
+ gap: 12,
423
+ },
424
+
425
+ iconWrapper: {
426
+ display: 'flex',
427
+ alignItems: 'center',
428
+ justifyContent: 'center',
429
+ width: 40,
430
+ height: 40,
431
+ borderRadius: 10,
432
+ backgroundColor: 'rgba(59, 130, 246, 0.15)',
433
+ color: 'var(--text-link)',
434
+ },
435
+
436
+ title: {
437
+ margin: 0,
438
+ fontSize: 18,
439
+ fontWeight: 600,
440
+ color: 'var(--text-primary)',
441
+ lineHeight: 1.2,
442
+ },
443
+
444
+ typeLabel: {
445
+ fontSize: 12,
446
+ color: 'var(--text-tertiary)',
447
+ fontWeight: 500,
448
+ textTransform: 'uppercase',
449
+ letterSpacing: '0.05em',
450
+ },
451
+
452
+ skipAllButton: {
453
+ padding: '6px 12px',
454
+ backgroundColor: 'transparent',
455
+ border: 'none',
456
+ borderRadius: 6,
457
+ color: 'var(--text-tertiary)',
458
+ fontSize: 13,
459
+ fontWeight: 500,
460
+ cursor: 'pointer',
461
+ transition: 'color 0.15s ease',
462
+ },
463
+
464
+ progressContainer: {
465
+ display: 'flex',
466
+ alignItems: 'center',
467
+ gap: 12,
468
+ marginBottom: 24,
469
+ },
470
+
471
+ progressBar: {
472
+ flex: 1,
473
+ height: 4,
474
+ backgroundColor: 'rgba(51, 65, 85, 0.5)',
475
+ borderRadius: 2,
476
+ overflow: 'hidden',
477
+ },
478
+
479
+ progressFill: {
480
+ height: '100%',
481
+ backgroundColor: 'var(--accent-default)',
482
+ borderRadius: 2,
483
+ transition: 'width 0.3s ease-out',
484
+ },
485
+
486
+ progressText: {
487
+ fontSize: 12,
488
+ color: 'var(--text-tertiary)',
489
+ fontWeight: 500,
490
+ whiteSpace: 'nowrap',
491
+ },
492
+
493
+ questionContainer: {
494
+ marginBottom: 16,
495
+ },
496
+
497
+ question: {
498
+ margin: '0 0 8px 0',
499
+ fontSize: 16,
500
+ fontWeight: 500,
501
+ color: 'var(--text-primary)',
502
+ lineHeight: 1.4,
503
+ },
504
+
505
+ hint: {
506
+ margin: 0,
507
+ fontSize: 13,
508
+ color: 'var(--text-secondary)',
509
+ lineHeight: 1.5,
510
+ },
511
+
512
+ matchedText: {
513
+ marginTop: 12,
514
+ padding: '8px 12px',
515
+ backgroundColor: 'rgba(251, 191, 36, 0.1)',
516
+ border: '1px solid rgba(251, 191, 36, 0.2)',
517
+ borderRadius: 8,
518
+ fontSize: 12,
519
+ color: 'var(--status-warning)',
520
+ },
521
+
522
+ matchedCode: {
523
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
524
+ backgroundColor: 'rgba(251, 191, 36, 0.15)',
525
+ padding: '2px 6px',
526
+ borderRadius: 4,
527
+ },
528
+
529
+ textarea: {
530
+ width: '100%',
531
+ minHeight: 80,
532
+ padding: 12,
533
+ backgroundColor: 'rgba(15, 23, 42, 0.8)',
534
+ border: '1px solid rgba(51, 65, 85, 0.8)',
535
+ borderRadius: 10,
536
+ color: 'var(--text-primary)',
537
+ fontSize: 14,
538
+ lineHeight: 1.5,
539
+ resize: 'vertical',
540
+ outline: 'none',
541
+ fontFamily: 'inherit',
542
+ transition: 'border-color 0.15s ease, box-shadow 0.15s ease',
543
+ boxSizing: 'border-box',
544
+ },
545
+
546
+ actions: {
547
+ display: 'flex',
548
+ alignItems: 'center',
549
+ justifyContent: 'space-between',
550
+ marginTop: 20,
551
+ },
552
+
553
+ actionsLeft: {
554
+ display: 'flex',
555
+ alignItems: 'center',
556
+ gap: 8,
557
+ },
558
+
559
+ actionsRight: {
560
+ display: 'flex',
561
+ alignItems: 'center',
562
+ gap: 8,
563
+ },
564
+
565
+ backButton: {
566
+ display: 'flex',
567
+ alignItems: 'center',
568
+ gap: 4,
569
+ padding: '8px 12px',
570
+ backgroundColor: 'transparent',
571
+ border: 'none',
572
+ borderRadius: 8,
573
+ color: 'var(--text-secondary)',
574
+ fontSize: 14,
575
+ fontWeight: 500,
576
+ cursor: 'pointer',
577
+ transition: 'color 0.15s ease',
578
+ },
579
+
580
+ skipButton: {
581
+ padding: '10px 16px',
582
+ backgroundColor: 'transparent',
583
+ border: '1px solid rgba(51, 65, 85, 0.8)',
584
+ borderRadius: 8,
585
+ color: 'var(--text-secondary)',
586
+ fontSize: 14,
587
+ fontWeight: 500,
588
+ cursor: 'pointer',
589
+ transition: 'all 0.15s ease',
590
+ },
591
+
592
+ nextButton: {
593
+ display: 'flex',
594
+ alignItems: 'center',
595
+ gap: 4,
596
+ padding: '10px 20px',
597
+ backgroundColor: 'var(--accent-default)',
598
+ border: 'none',
599
+ borderRadius: 8,
600
+ color: 'var(--text-inverse)',
601
+ fontSize: 14,
602
+ fontWeight: 600,
603
+ cursor: 'pointer',
604
+ transition: 'background-color 0.15s ease',
605
+ },
606
+
607
+ keyboardHints: {
608
+ display: 'flex',
609
+ alignItems: 'center',
610
+ justifyContent: 'center',
611
+ gap: 16,
612
+ marginTop: 16,
613
+ paddingTop: 16,
614
+ borderTop: '1px solid rgba(51, 65, 85, 0.5)',
615
+ },
616
+
617
+ keyboardHint: {
618
+ display: 'flex',
619
+ alignItems: 'center',
620
+ gap: 4,
621
+ fontSize: 11,
622
+ color: 'var(--text-tertiary)',
623
+ },
624
+
625
+ kbd: {
626
+ display: 'inline-flex',
627
+ alignItems: 'center',
628
+ justifyContent: 'center',
629
+ minWidth: 20,
630
+ padding: '2px 6px',
631
+ backgroundColor: 'rgba(51, 65, 85, 0.5)',
632
+ border: '1px solid rgba(71, 85, 105, 0.5)',
633
+ borderRadius: 4,
634
+ fontSize: 10,
635
+ fontWeight: 500,
636
+ color: 'var(--text-secondary)',
637
+ fontFamily: 'inherit',
638
+ },
639
+
640
+ answeredIndicator: {
641
+ position: 'absolute',
642
+ bottom: -10,
643
+ left: '50%',
644
+ transform: 'translateX(-50%)',
645
+ padding: '4px 12px',
646
+ backgroundColor: 'rgba(34, 197, 94, 0.15)',
647
+ border: '1px solid rgba(34, 197, 94, 0.3)',
648
+ borderRadius: 12,
649
+ fontSize: 11,
650
+ fontWeight: 500,
651
+ color: 'var(--status-success)',
652
+ },
653
+ };
654
+
655
+ // Default export for convenience
656
+ export default ClarificationQuestions;