documentation-hub 5.7.2

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 (271) hide show
  1. package/.eslintrc.json +43 -0
  2. package/.github/workflows/build.yml +64 -0
  3. package/.github/workflows/ci.yml +39 -0
  4. package/.vscode/extensions.json +3 -0
  5. package/Current.md +97 -0
  6. package/DocHub_Image.png +0 -0
  7. package/README.md +666 -0
  8. package/USER_GUIDE.md +1173 -0
  9. package/Updater.md +311 -0
  10. package/build/256x256.png +0 -0
  11. package/build/512x512.png +0 -0
  12. package/build/app-update.yml +4 -0
  13. package/build/create-icon.js +208 -0
  14. package/build/icon.ico +0 -0
  15. package/build/icon.png +0 -0
  16. package/build/icon_1024x1024.png +0 -0
  17. package/dist/assets/Analytics-BpsG9895.js +1 -0
  18. package/dist/assets/Card-IAZin8kp.js +1 -0
  19. package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
  20. package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
  21. package/dist/assets/Documents-CqZ25axS.js +1 -0
  22. package/dist/assets/Input-l89xwXBi.js +1 -0
  23. package/dist/assets/Reporting-DqdHJY_a.js +1 -0
  24. package/dist/assets/Search-XNbu5z_3.js +1 -0
  25. package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
  26. package/dist/assets/Sessions-ClZOPYNc.js +1 -0
  27. package/dist/assets/Settings-DUEHGURa.js +11 -0
  28. package/dist/assets/index-8xUe8ptc.js +24 -0
  29. package/dist/assets/index-RYyJqF7O.css +1 -0
  30. package/dist/assets/path-BkOl0AGO.js +1 -0
  31. package/dist/assets/promises-ID_B9S-h.js +1 -0
  32. package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
  33. package/dist/assets/useToast-yRSO1dkm.js +1 -0
  34. package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
  35. package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
  36. package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
  37. package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
  38. package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
  39. package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
  40. package/dist/electron/main-CXkNtyv-.js +19789 -0
  41. package/dist/electron/main.js +5 -0
  42. package/dist/electron/preload.js +1 -0
  43. package/dist/icon.png +0 -0
  44. package/dist/index.html +27 -0
  45. package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
  46. package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
  47. package/docs/README.md +115 -0
  48. package/docs/TOC_WIRING_GUIDE.md +344 -0
  49. package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
  50. package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
  51. package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
  52. package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
  53. package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
  54. package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
  55. package/docs/analysis/List_Implementation.md +206 -0
  56. package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
  57. package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
  58. package/docs/analysis/RefactorStyles.md +852 -0
  59. package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
  60. package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
  61. package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
  62. package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
  63. package/docs/analysis/github-issues-to-create.md +237 -0
  64. package/docs/api/API_README.md +538 -0
  65. package/docs/api/API_REFERENCE.md +751 -0
  66. package/docs/api/TYPE_DEFINITIONS.md +869 -0
  67. package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
  68. package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
  69. package/docs/docxmlater-readme.md +1341 -0
  70. package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
  71. package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
  72. package/docs/fixes/README.md +37 -0
  73. package/docs/github-issues/issue-1-body.md +125 -0
  74. package/docs/github-issues/issue-10-body.md +850 -0
  75. package/docs/github-issues/issue-2-body.md +200 -0
  76. package/docs/github-issues/issue-3-body.md +270 -0
  77. package/docs/github-issues/issue-4-body.md +169 -0
  78. package/docs/github-issues/issue-5-body.md +173 -0
  79. package/docs/github-issues/issue-6-body.md +158 -0
  80. package/docs/github-issues/issue-7-body.md +171 -0
  81. package/docs/github-issues/issue-8-body.md +407 -0
  82. package/docs/github-issues/issue-9-body.md +515 -0
  83. package/docs/github-issues/issue-tracker.md +274 -0
  84. package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
  85. package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
  86. package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
  87. package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
  88. package/docs/implementation/RefactorStyles.md +588 -0
  89. package/docs/implementation/implement-plan.md +489 -0
  90. package/docs/implementation/missing-helpers-implementation.md +391 -0
  91. package/docs/implementation/refactor-plan.md +520 -0
  92. package/docs/implementation/session-implementation-complete.md +233 -0
  93. package/docs/implementation/session-management-plan.md +250 -0
  94. package/docs/setup-checklist.md +77 -0
  95. package/docs/versions/changelog.md +345 -0
  96. package/electron/customUpdater.ts +656 -0
  97. package/electron/main.ts +2441 -0
  98. package/electron/memoryConfig.ts +187 -0
  99. package/electron/preload.ts +394 -0
  100. package/electron/proxyConfig.ts +340 -0
  101. package/electron/services/BackupService.ts +452 -0
  102. package/electron/services/DictionaryService.ts +402 -0
  103. package/electron/services/LocalDictionaryLookupService.ts +147 -0
  104. package/electron/services/PowerAutomateApiService.ts +231 -0
  105. package/electron/services/SharePointSyncService.ts +474 -0
  106. package/electron/windowsCertStore.ts +427 -0
  107. package/electron/zscalerConfig.ts +381 -0
  108. package/eslint.config.js +92 -0
  109. package/jest.config.js +52 -0
  110. package/package.json +214 -0
  111. package/postcss.config.mjs +6 -0
  112. package/public/icon.png +0 -0
  113. package/publish-release.ps1 +5 -0
  114. package/renovate.json +30 -0
  115. package/src/App.tsx +216 -0
  116. package/src/__mocks__/p-limit.js +12 -0
  117. package/src/__mocks__/styleMock.js +1 -0
  118. package/src/components/common/BugReportButton.tsx +44 -0
  119. package/src/components/common/BugReportDialog.tsx +193 -0
  120. package/src/components/common/Button.tsx +153 -0
  121. package/src/components/common/Card.tsx +86 -0
  122. package/src/components/common/ColorPickerDialog.tsx +177 -0
  123. package/src/components/common/ConfirmDialog.tsx +96 -0
  124. package/src/components/common/DebugConsole.tsx +275 -0
  125. package/src/components/common/EmptyState.tsx +183 -0
  126. package/src/components/common/ErrorBoundary.tsx +98 -0
  127. package/src/components/common/ErrorDetailsDialog.tsx +153 -0
  128. package/src/components/common/ErrorFallback.tsx +218 -0
  129. package/src/components/common/Input.tsx +109 -0
  130. package/src/components/common/Skeleton.tsx +184 -0
  131. package/src/components/common/SplashScreen.tsx +81 -0
  132. package/src/components/common/Toast.tsx +155 -0
  133. package/src/components/common/Tooltip.tsx +79 -0
  134. package/src/components/common/UpdateNotification.tsx +320 -0
  135. package/src/components/comparison/ComparisonWindow.tsx +374 -0
  136. package/src/components/comparison/SideBySideDiff.tsx +486 -0
  137. package/src/components/comparison/index.ts +8 -0
  138. package/src/components/document/DocumentUploader.tsx +288 -0
  139. package/src/components/document/HyperlinkPreview.tsx +430 -0
  140. package/src/components/document/HyperlinkService.md +1484 -0
  141. package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
  142. package/src/components/document/InlineChangesView.tsx +707 -0
  143. package/src/components/document/ProcessingProgress.tsx +303 -0
  144. package/src/components/document/ProcessingResults.tsx +256 -0
  145. package/src/components/document/TrackedChangesDetail.tsx +530 -0
  146. package/src/components/document/TrackedChangesPanel.tsx +546 -0
  147. package/src/components/document/VirtualDocumentList.tsx +240 -0
  148. package/src/components/editor/DocumentEditor.tsx +723 -0
  149. package/src/components/editor/DocumentEditorModal.tsx +640 -0
  150. package/src/components/editor/EditorQuickActions.tsx +502 -0
  151. package/src/components/editor/EditorToolbar.tsx +312 -0
  152. package/src/components/editor/TableEditor.tsx +926 -0
  153. package/src/components/editor/index.ts +18 -0
  154. package/src/components/layout/Header.tsx +190 -0
  155. package/src/components/layout/Sidebar.tsx +313 -0
  156. package/src/components/layout/TitleBar.tsx +190 -0
  157. package/src/components/navigation/CommandPalette.tsx +233 -0
  158. package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
  159. package/src/components/sessions/ChangeItem.tsx +408 -0
  160. package/src/components/sessions/ChangeViewer.tsx +1155 -0
  161. package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
  162. package/src/components/sessions/ProcessingOptions.tsx +297 -0
  163. package/src/components/sessions/ReplacementsTab.tsx +438 -0
  164. package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
  165. package/src/components/sessions/SessionManager.tsx +188 -0
  166. package/src/components/sessions/StylesEditor.tsx +1335 -0
  167. package/src/components/sessions/TabContainer.tsx +151 -0
  168. package/src/components/sessions/VirtualSessionList.tsx +157 -0
  169. package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
  170. package/src/components/settings/CertificateManager.tsx +410 -0
  171. package/src/components/settings/SegmentedControl.tsx +88 -0
  172. package/src/components/settings/SettingRow.tsx +52 -0
  173. package/src/contexts/GlobalStatsContext.tsx +396 -0
  174. package/src/contexts/SessionContext.tsx +2129 -0
  175. package/src/contexts/ThemeContext.tsx +428 -0
  176. package/src/contexts/UserSettingsContext.tsx +290 -0
  177. package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
  178. package/src/global.d.ts +273 -0
  179. package/src/hooks/useDocumentQueue.tsx +210 -0
  180. package/src/hooks/useToast.tsx +55 -0
  181. package/src/main.tsx +10 -0
  182. package/src/pages/Analytics.tsx +386 -0
  183. package/src/pages/CurrentSession.tsx +1174 -0
  184. package/src/pages/Dashboard.tsx +319 -0
  185. package/src/pages/Documents.tsx +317 -0
  186. package/src/pages/Projects.tsx +250 -0
  187. package/src/pages/Reporting.tsx +386 -0
  188. package/src/pages/Search.tsx +349 -0
  189. package/src/pages/Sessions.tsx +285 -0
  190. package/src/pages/Settings.tsx +2662 -0
  191. package/src/services/HyperlinkService.ts +1085 -0
  192. package/src/services/document/DocXMLaterProcessor.ts +617 -0
  193. package/src/services/document/DocumentProcessingComparison.ts +856 -0
  194. package/src/services/document/DocumentSnapshotService.ts +575 -0
  195. package/src/services/document/WordDocumentProcessor.ts +10509 -0
  196. package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
  197. package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
  198. package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
  199. package/src/services/document/blanklines/BlankLineManager.ts +658 -0
  200. package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
  201. package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
  202. package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
  203. package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
  204. package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
  205. package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
  206. package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
  207. package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
  208. package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
  209. package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
  210. package/src/services/document/blanklines/index.ts +67 -0
  211. package/src/services/document/blanklines/rules/additionRules.ts +337 -0
  212. package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
  213. package/src/services/document/blanklines/rules/removalRules.ts +362 -0
  214. package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
  215. package/src/services/document/blanklines/types.ts +29 -0
  216. package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
  217. package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
  218. package/src/services/document/helpers/whitespace.ts +117 -0
  219. package/src/services/document/list/ListNormalizer.ts +947 -0
  220. package/src/services/document/list/index.ts +45 -0
  221. package/src/services/document/list/list-detection.ts +275 -0
  222. package/src/services/document/list/list-types.ts +162 -0
  223. package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
  224. package/src/services/document/processors/ListProcessor.ts +257 -0
  225. package/src/services/document/processors/StructureProcessor.ts +176 -0
  226. package/src/services/document/processors/StyleProcessor.ts +389 -0
  227. package/src/services/document/processors/TableProcessor.ts +2238 -0
  228. package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
  229. package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
  230. package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
  231. package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
  232. package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
  233. package/src/services/document/processors/index.ts +28 -0
  234. package/src/services/document/types/docx-processing.ts +310 -0
  235. package/src/services/editor/EditorActionHandlers.ts +901 -0
  236. package/src/services/editor/index.ts +13 -0
  237. package/src/setupTests.ts +47 -0
  238. package/src/styles/global.css +782 -0
  239. package/src/types/backup.ts +132 -0
  240. package/src/types/dictionary.ts +125 -0
  241. package/src/types/document-processing.ts +331 -0
  242. package/src/types/docxmlater-augments.d.ts +142 -0
  243. package/src/types/editor.ts +280 -0
  244. package/src/types/electron.ts +340 -0
  245. package/src/types/globalStats.ts +155 -0
  246. package/src/types/hyperlink.ts +471 -0
  247. package/src/types/operations.ts +354 -0
  248. package/src/types/session.ts +427 -0
  249. package/src/types/settings.ts +112 -0
  250. package/src/utils/MemoryMonitor.ts +248 -0
  251. package/src/utils/cn.ts +6 -0
  252. package/src/utils/colorConvert.ts +306 -0
  253. package/src/utils/diffUtils.ts +347 -0
  254. package/src/utils/documentUtils.ts +202 -0
  255. package/src/utils/electronGuard.ts +62 -0
  256. package/src/utils/indexedDB.ts +915 -0
  257. package/src/utils/logger.ts +717 -0
  258. package/src/utils/pathSecurity.ts +232 -0
  259. package/src/utils/pathValidator.ts +236 -0
  260. package/src/utils/processingTimeEstimator.ts +153 -0
  261. package/src/utils/safeJsonParse.ts +62 -0
  262. package/src/utils/textSanitizer.ts +162 -0
  263. package/src/utils/urlHelpers.ts +304 -0
  264. package/src/utils/urlPatterns.ts +198 -0
  265. package/src/utils/urlSanitizer.ts +152 -0
  266. package/src/vite-env.d.ts +11 -0
  267. package/tsconfig.electron.json +19 -0
  268. package/tsconfig.json +36 -0
  269. package/tsconfig.node.json +12 -0
  270. package/typedoc.json +45 -0
  271. package/vite.config.ts +152 -0
@@ -0,0 +1,314 @@
1
+ /**
2
+ * DocumentComparisonModal - Side-by-side comparison of original vs processed documents
3
+ *
4
+ * DEFERRED FEATURE: This component is currently disabled in the UI.
5
+ * The side-by-side comparison feature may be implemented in a future release.
6
+ * See ChangeViewer.tsx for the deferred button implementation.
7
+ *
8
+ * Opens a modal dialog displaying SideBySideDiff for comparing document states
9
+ * before and after processing. Supports navigation between multiple documents.
10
+ */
11
+
12
+ import { useState, useEffect, useCallback } from 'react';
13
+ import * as Dialog from '@radix-ui/react-dialog';
14
+ import {
15
+ X,
16
+ ChevronLeft,
17
+ ChevronRight,
18
+ FileText,
19
+ RefreshCw,
20
+ AlertCircle,
21
+ } from 'lucide-react';
22
+ import { cn } from '@/utils/cn';
23
+ import { Button } from '@/components/common/Button';
24
+ import { SideBySideDiff } from '@/components/comparison/SideBySideDiff';
25
+ import { DocumentSnapshotService } from '@/services/document/DocumentSnapshotService';
26
+ import type { Document } from '@/types/session';
27
+ import logger from '@/utils/logger';
28
+
29
+ interface DocumentComparisonModalProps {
30
+ /** Whether the modal is open */
31
+ isOpen: boolean;
32
+ /** Callback when modal should close */
33
+ onClose: () => void;
34
+ /** Session ID for snapshot lookup */
35
+ sessionId: string;
36
+ /** List of completed documents to compare */
37
+ documents: Document[];
38
+ /** Initial document index to display */
39
+ initialDocumentIndex?: number;
40
+ }
41
+
42
+ interface ComparisonState {
43
+ originalText: string[];
44
+ processedText: string[];
45
+ isLoading: boolean;
46
+ error: string | null;
47
+ }
48
+
49
+ export function DocumentComparisonModal({
50
+ isOpen,
51
+ onClose,
52
+ sessionId,
53
+ documents,
54
+ initialDocumentIndex = 0,
55
+ }: DocumentComparisonModalProps) {
56
+ const [currentDocIndex, setCurrentDocIndex] = useState(initialDocumentIndex);
57
+ const [comparisonState, setComparisonState] = useState<ComparisonState>({
58
+ originalText: [],
59
+ processedText: [],
60
+ isLoading: false,
61
+ error: null,
62
+ });
63
+
64
+ const currentDoc = documents[currentDocIndex];
65
+
66
+ // Load comparison data for current document
67
+ const loadComparisonData = useCallback(async () => {
68
+ if (!currentDoc || !currentDoc.path) {
69
+ setComparisonState({
70
+ originalText: [],
71
+ processedText: [],
72
+ isLoading: false,
73
+ error: 'Document path not available',
74
+ });
75
+ return;
76
+ }
77
+
78
+ setComparisonState((prev) => ({ ...prev, isLoading: true, error: null }));
79
+
80
+ try {
81
+ // Load original text from snapshot
82
+ const snapshot = await DocumentSnapshotService.getSnapshot(
83
+ sessionId,
84
+ currentDoc.id
85
+ );
86
+
87
+ if (!snapshot) {
88
+ setComparisonState({
89
+ originalText: [],
90
+ processedText: [],
91
+ isLoading: false,
92
+ error: 'Original document snapshot not available. The snapshot may have expired or was not captured.',
93
+ });
94
+ return;
95
+ }
96
+
97
+ // Load processed text from current document file
98
+ const result = await window.electronAPI.extractDocumentText(currentDoc.path);
99
+
100
+ if (!result.success || !result.textContent) {
101
+ setComparisonState({
102
+ originalText: snapshot.textContent,
103
+ processedText: [],
104
+ isLoading: false,
105
+ error: result.error || 'Failed to extract processed document text',
106
+ });
107
+ return;
108
+ }
109
+
110
+ setComparisonState({
111
+ originalText: snapshot.textContent,
112
+ processedText: result.textContent,
113
+ isLoading: false,
114
+ error: null,
115
+ });
116
+
117
+ logger.info(
118
+ `[DocumentComparisonModal] Loaded comparison for ${currentDoc.name}: ` +
119
+ `${snapshot.textContent.length} original paragraphs, ${result.textContent.length} processed paragraphs`
120
+ );
121
+ } catch (error) {
122
+ logger.error('[DocumentComparisonModal] Error loading comparison:', error);
123
+ setComparisonState({
124
+ originalText: [],
125
+ processedText: [],
126
+ isLoading: false,
127
+ error: error instanceof Error ? error.message : 'Failed to load comparison data',
128
+ });
129
+ }
130
+ }, [currentDoc, sessionId]);
131
+
132
+ // Load data when document changes
133
+ useEffect(() => {
134
+ if (isOpen && currentDoc) {
135
+ loadComparisonData();
136
+ }
137
+ }, [isOpen, currentDoc, loadComparisonData]);
138
+
139
+ // Reset state when modal closes
140
+ useEffect(() => {
141
+ if (!isOpen) {
142
+ setCurrentDocIndex(initialDocumentIndex);
143
+ setComparisonState({
144
+ originalText: [],
145
+ processedText: [],
146
+ isLoading: false,
147
+ error: null,
148
+ });
149
+ }
150
+ }, [isOpen, initialDocumentIndex]);
151
+
152
+ // Navigation handlers
153
+ const handlePrevious = useCallback(() => {
154
+ if (currentDocIndex > 0) {
155
+ setCurrentDocIndex((prev) => prev - 1);
156
+ }
157
+ }, [currentDocIndex]);
158
+
159
+ const handleNext = useCallback(() => {
160
+ if (currentDocIndex < documents.length - 1) {
161
+ setCurrentDocIndex((prev) => prev + 1);
162
+ }
163
+ }, [currentDocIndex, documents.length]);
164
+
165
+ // Keyboard navigation
166
+ useEffect(() => {
167
+ if (!isOpen) return;
168
+
169
+ const handleKeyDown = (e: KeyboardEvent) => {
170
+ if (e.key === 'ArrowLeft' && currentDocIndex > 0) {
171
+ handlePrevious();
172
+ } else if (e.key === 'ArrowRight' && currentDocIndex < documents.length - 1) {
173
+ handleNext();
174
+ } else if (e.key === 'Escape') {
175
+ onClose();
176
+ }
177
+ };
178
+
179
+ window.addEventListener('keydown', handleKeyDown);
180
+ return () => window.removeEventListener('keydown', handleKeyDown);
181
+ }, [isOpen, currentDocIndex, documents.length, handlePrevious, handleNext, onClose]);
182
+
183
+ if (documents.length === 0) {
184
+ return null;
185
+ }
186
+
187
+ return (
188
+ <Dialog.Root open={isOpen} onOpenChange={(open) => !open && onClose()}>
189
+ <Dialog.Portal>
190
+ <Dialog.Overlay className="fixed inset-0 bg-black/60 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 z-50" />
191
+ <Dialog.Content
192
+ aria-describedby={undefined}
193
+ className={cn(
194
+ 'fixed left-[50%] top-[50%] z-50 translate-x-[-50%] translate-y-[-50%]',
195
+ 'w-[95vw] max-w-6xl h-[90vh] rounded-lg border border-border bg-card shadow-xl',
196
+ 'flex flex-col overflow-hidden',
197
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out',
198
+ 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
199
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
200
+ 'data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]',
201
+ 'data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]'
202
+ )}
203
+ >
204
+ {/* Header */}
205
+ <div className="flex items-center justify-between px-4 py-3 border-b border-border bg-muted/30">
206
+ <div className="flex items-center gap-3">
207
+ <FileText className="w-5 h-5 text-primary" />
208
+ <Dialog.Title className="font-semibold text-foreground">
209
+ Compare: {currentDoc?.name || 'Document'}
210
+ </Dialog.Title>
211
+ </div>
212
+
213
+ <div className="flex items-center gap-4">
214
+ {/* Navigation */}
215
+ {documents.length > 1 && (
216
+ <div className="flex items-center gap-2">
217
+ <Button
218
+ variant="ghost"
219
+ size="sm"
220
+ onClick={handlePrevious}
221
+ disabled={currentDocIndex === 0}
222
+ className="h-8 w-8 p-0"
223
+ title="Previous document (Left Arrow)"
224
+ >
225
+ <ChevronLeft className="w-4 h-4" />
226
+ </Button>
227
+ <span className="text-sm text-muted-foreground min-w-[4rem] text-center">
228
+ {currentDocIndex + 1} / {documents.length}
229
+ </span>
230
+ <Button
231
+ variant="ghost"
232
+ size="sm"
233
+ onClick={handleNext}
234
+ disabled={currentDocIndex === documents.length - 1}
235
+ className="h-8 w-8 p-0"
236
+ title="Next document (Right Arrow)"
237
+ >
238
+ <ChevronRight className="w-4 h-4" />
239
+ </Button>
240
+ </div>
241
+ )}
242
+
243
+ {/* Close button */}
244
+ <Dialog.Close asChild>
245
+ <Button
246
+ variant="ghost"
247
+ size="sm"
248
+ className="h-8 w-8 p-0"
249
+ title="Close (Escape)"
250
+ >
251
+ <X className="w-4 h-4" />
252
+ </Button>
253
+ </Dialog.Close>
254
+ </div>
255
+ </div>
256
+
257
+ {/* Content */}
258
+ <div className="flex-1 overflow-hidden">
259
+ {comparisonState.isLoading ? (
260
+ <div className="flex flex-col items-center justify-center h-full text-muted-foreground">
261
+ <RefreshCw className="w-10 h-10 mb-4 animate-spin" />
262
+ <p className="text-lg font-medium">Loading comparison data...</p>
263
+ <p className="text-sm mt-1">Extracting document content</p>
264
+ </div>
265
+ ) : comparisonState.error ? (
266
+ <div className="flex flex-col items-center justify-center h-full text-muted-foreground">
267
+ <AlertCircle className="w-10 h-10 mb-4 text-destructive" />
268
+ <p className="text-lg font-medium text-destructive">Unable to load comparison</p>
269
+ <p className="text-sm mt-2 max-w-md text-center">{comparisonState.error}</p>
270
+ <Button
271
+ variant="outline"
272
+ size="sm"
273
+ className="mt-4"
274
+ onClick={loadComparisonData}
275
+ >
276
+ <RefreshCw className="w-4 h-4 mr-2" />
277
+ Retry
278
+ </Button>
279
+ </div>
280
+ ) : (
281
+ <SideBySideDiff
282
+ originalContent={comparisonState.originalText}
283
+ modifiedContent={comparisonState.processedText}
284
+ syncScroll={true}
285
+ showLineNumbers={true}
286
+ collapseUnchanged={false}
287
+ height="100%"
288
+ />
289
+ )}
290
+ </div>
291
+
292
+ {/* Footer */}
293
+ <div className="flex items-center justify-between px-4 py-2 border-t border-border bg-muted/20 text-xs text-muted-foreground">
294
+ <div>
295
+ Original: {comparisonState.originalText.length} paragraphs |
296
+ Processed: {comparisonState.processedText.length} paragraphs
297
+ </div>
298
+ <div className="flex items-center gap-2">
299
+ <span className="px-2 py-0.5 rounded bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300">
300
+ Removed
301
+ </span>
302
+ <span className="px-2 py-0.5 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-300">
303
+ Added
304
+ </span>
305
+ <span className="px-2 py-0.5 rounded bg-yellow-100 dark:bg-yellow-900/30 text-yellow-700 dark:text-yellow-300">
306
+ Modified
307
+ </span>
308
+ </div>
309
+ </div>
310
+ </Dialog.Content>
311
+ </Dialog.Portal>
312
+ </Dialog.Root>
313
+ );
314
+ }
@@ -0,0 +1,297 @@
1
+ import { cn } from '@/utils/cn';
2
+ import { motion } from 'framer-motion';
3
+ import { Check } from 'lucide-react';
4
+ import { useCallback, useMemo } from 'react';
5
+ import { RevisionHandlingOptions } from './RevisionHandlingOptions';
6
+
7
+ /**
8
+ * TYPE SAFETY: Define processing groups as const tuple for runtime validation
9
+ * This allows us to derive the type from the value, ensuring consistency
10
+ * between runtime checks and TypeScript types.
11
+ */
12
+ export const PROCESSING_GROUPS = ['text', 'hyperlinks', 'structure', 'lists'] as const;
13
+ export type ProcessingGroup = (typeof PROCESSING_GROUPS)[number];
14
+
15
+ export interface ProcessingOption {
16
+ id: string;
17
+ label: string;
18
+ group: ProcessingGroup;
19
+ enabled: boolean;
20
+ }
21
+
22
+ // User-friendly descriptions for the right panel
23
+ const optionDescriptions: Record<string, string> = {
24
+ // Text Formatting
25
+ 'remove-italics': 'Removes all italic text formatting',
26
+ 'normalize-dashes': 'Converts special dashes (em-dash, en-dash) to regular hyphens',
27
+ 'preserve-red-font': 'Prevents changes to text that is colored red',
28
+ 'replace-outdated-titles': 'Updates a hyperlink\'s display text to match the title within theSource if different',
29
+ 'validate-document-styles': 'Applies your custom style settings from the Styles tab',
30
+ 'correct-misapplied-styles': 'Fixes paragraphs with incorrectly applied TOC or Hyperlink styles so they receive proper formatting',
31
+
32
+ // Links & Navigation
33
+ 'update-top-hyperlinks': 'Adds or updates links that jump to the document start',
34
+ 'update-toc-hyperlinks': 'Creates or updates clickable links in the table of contents',
35
+ 'force-remove-heading1-toc': 'Removes the main document title from the table of contents',
36
+ 'fix-internal-hyperlinks': 'Fixes broken links that point to other parts of the document',
37
+ 'fix-content-ids': 'Appends Content ID to the end of theSource Hyperlinks',
38
+
39
+ // Document Structure
40
+ 'center-border-images': 'Centers and borders images if image is bigger than 100x100 pixels',
41
+ 'remove-whitespace': 'Removes unnecessary spaces, tabs, and blank areas',
42
+ 'remove-paragraph-lines': 'Makes spacing between paragraphs consistent',
43
+ 'remove-headers-footers': 'Removes text from document headers and footers',
44
+ 'add-document-warning': 'Adds a standard disclaimer notice if missing',
45
+ 'validate-header2-tables': 'Formats 1x1 Heading 2 tables to ensure standardization across document',
46
+
47
+ // Lists & Tables
48
+ 'list-indentation': 'Corrects the indentation of bulleted and numbered lists',
49
+ 'bullet-uniformity': 'Makes bullet styles consistent throughout',
50
+ 'normalize-table-lists': 'Converts typed list prefixes (1., A., •) to proper Word formatting and fixes mixed lists',
51
+ 'smart-tables': 'Applies consistent styling to all tables',
52
+ 'adjust-table-padding': 'Adjusts the space inside table cells',
53
+ 'standardize-table-borders': 'Makes table border styles uniform',
54
+ 'set-landscape-margins': 'Sets document to landscape orientation with 1-inch margins',
55
+ };
56
+
57
+ export const defaultOptions: ProcessingOption[] = [
58
+ // Text Formatting Group
59
+ { id: 'remove-italics', label: 'Remove Italics', group: 'text', enabled: true },
60
+ { id: 'normalize-dashes', label: 'Standardize Dashes', group: 'text', enabled: true },
61
+ { id: 'preserve-red-font', label: 'Keep Red Text', group: 'text', enabled: false },
62
+ { id: 'replace-outdated-titles', label: 'Update theSource Link Titles', group: 'hyperlinks', enabled: true },
63
+ { id: 'validate-document-styles', label: 'Apply Custom Styles', group: 'text', enabled: true },
64
+ { id: 'correct-misapplied-styles', label: 'Fix Misapplied Styles', group: 'text', enabled: true },
65
+
66
+ // Links & Navigation Group
67
+ { id: 'update-top-hyperlinks', label: '"Top of Document" Hyperlinks', group: 'hyperlinks', enabled: true },
68
+ { id: 'update-toc-hyperlinks', label: 'Table of Contents Hyperlinks', group: 'hyperlinks', enabled: true },
69
+ { id: 'force-remove-heading1-toc', label: 'Remove Title from TOC', group: 'hyperlinks', enabled: true },
70
+ { id: 'fix-internal-hyperlinks', label: 'Internal Hyperlinks', group: 'hyperlinks', enabled: true },
71
+ { id: 'fix-content-ids', label: 'Content ID References', group: 'hyperlinks', enabled: true },
72
+
73
+ // Document Structure Group
74
+ { id: 'center-border-images', label: 'Center and Border Images', group: 'structure', enabled: true },
75
+ { id: 'remove-whitespace', label: 'Clean Up Spaces', group: 'structure', enabled: true },
76
+ { id: 'remove-paragraph-lines', label: 'Standardize Blank Lines', group: 'structure', enabled: true },
77
+ { id: 'remove-headers-footers', label: 'Clear Headers/Footers', group: 'structure', enabled: true },
78
+ { id: 'add-document-warning', label: 'Add Missing Disclaimer', group: 'structure', enabled: true },
79
+ { id: 'validate-header2-tables', label: 'Heading 2 Tables', group: 'structure', enabled: true },
80
+ { id: 'set-landscape-margins', label: 'Landscape Layout', group: 'structure', enabled: true },
81
+
82
+ // Lists & Tables Group
83
+ { id: 'list-indentation', label: 'Fix List Spacing', group: 'lists', enabled: true },
84
+ { id: 'bullet-uniformity', label: 'Standardize Bullets', group: 'lists', enabled: true },
85
+ { id: 'normalize-table-lists', label: 'Fix Typed List Prefixes', group: 'lists', enabled: true },
86
+ { id: 'smart-tables', label: 'Format Tables', group: 'lists', enabled: true },
87
+ { id: 'adjust-table-padding', label: 'Table Cell Spacing', group: 'lists', enabled: true },
88
+ { id: 'standardize-table-borders', label: 'Fix Table Borders', group: 'lists', enabled: true },
89
+ ];
90
+
91
+ // User-friendly group labels
92
+ const groupLabels: Record<ProcessingGroup, string> = {
93
+ text: 'Text Formatting',
94
+ hyperlinks: 'Links & Navigation',
95
+ structure: 'Document Structure',
96
+ lists: 'Lists & Tables',
97
+ };
98
+
99
+ interface ProcessingOptionsProps {
100
+ sessionId?: string;
101
+ options: ProcessingOption[]; // Fully controlled - no "initial" prefix
102
+ onOptionsChange: (options: ProcessingOption[]) => void; // Required, not optional
103
+ // Revision handling options
104
+ autoAcceptRevisions?: boolean;
105
+ onAutoAcceptRevisionsChange?: (autoAccept: boolean) => void;
106
+ }
107
+
108
+ export function ProcessingOptions({
109
+ options,
110
+ onOptionsChange,
111
+ autoAcceptRevisions = false,
112
+ onAutoAcceptRevisionsChange,
113
+ }: ProcessingOptionsProps) {
114
+ // Calculate master toggle state from props (derived state, not stored)
115
+ const masterToggle = useMemo(() => {
116
+ return options.every((opt) => opt.enabled);
117
+ }, [options]);
118
+
119
+ const toggleOption = useCallback(
120
+ (optionId: string) => {
121
+ const updatedOptions = options.map((opt) =>
122
+ opt.id === optionId ? { ...opt, enabled: !opt.enabled } : opt
123
+ );
124
+ onOptionsChange(updatedOptions);
125
+ },
126
+ [options, onOptionsChange]
127
+ );
128
+
129
+ const toggleAll = useCallback(() => {
130
+ const newState = !masterToggle;
131
+ const updatedOptions = options.map((opt) => ({ ...opt, enabled: newState }));
132
+ onOptionsChange(updatedOptions);
133
+ }, [masterToggle, options, onOptionsChange]);
134
+
135
+ const toggleGroup = useCallback(
136
+ (group: string) => {
137
+ const groupOptions = options.filter((opt) => opt.group === group);
138
+ const allEnabled = groupOptions.every((opt) => opt.enabled);
139
+
140
+ const updatedOptions = options.map((opt) =>
141
+ opt.group === group ? { ...opt, enabled: !allEnabled } : opt
142
+ );
143
+ onOptionsChange(updatedOptions);
144
+ },
145
+ [options, onOptionsChange]
146
+ );
147
+
148
+ const groupedOptions = options.reduce(
149
+ (acc, option) => {
150
+ if (!acc[option.group]) {
151
+ acc[option.group] = [];
152
+ }
153
+ acc[option.group].push(option);
154
+ return acc;
155
+ },
156
+ {} as Record<string, ProcessingOption[]>
157
+ );
158
+
159
+ // Get enabled options grouped by category for the right panel
160
+ const enabledByGroup = useMemo(() => {
161
+ const result: Record<string, ProcessingOption[]> = {};
162
+ for (const group of PROCESSING_GROUPS) {
163
+ const enabledInGroup = options.filter((opt) => opt.group === group && opt.enabled);
164
+ if (enabledInGroup.length > 0) {
165
+ result[group] = enabledInGroup;
166
+ }
167
+ }
168
+ return result;
169
+ }, [options]);
170
+
171
+ const hasEnabledOptions = Object.keys(enabledByGroup).length > 0;
172
+
173
+ return (
174
+ <div className="space-y-6">
175
+ {/* Two-Column Layout: Options on Left, Descriptions on Right */}
176
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
177
+ {/* Left Column: Options */}
178
+ <div className="space-y-4">
179
+ {Object.entries(groupedOptions).map(([group, groupOptions]) => {
180
+ const allEnabled = groupOptions.every((opt) => opt.enabled);
181
+ const someEnabled = groupOptions.some((opt) => opt.enabled);
182
+
183
+ return (
184
+ <div key={group} className="space-y-2">
185
+ <button
186
+ type="button"
187
+ className="flex items-center gap-2 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded"
188
+ onClick={() => toggleGroup(group)}
189
+ role="checkbox"
190
+ aria-checked={allEnabled ? true : someEnabled ? 'mixed' : false}
191
+ aria-label={`Toggle ${groupLabels[group as ProcessingGroup]}`}
192
+ >
193
+ <div
194
+ aria-hidden="true"
195
+ className={cn(
196
+ 'w-5 h-5 rounded border-2 flex items-center justify-center transition-all',
197
+ allEnabled
198
+ ? 'bg-primary border-primary checkbox-checked'
199
+ : someEnabled
200
+ ? 'bg-primary/50 border-primary'
201
+ : 'border-border hover:border-primary/50'
202
+ )}
203
+ >
204
+ {(allEnabled || someEnabled) && (
205
+ <Check className="w-3 h-3 text-primary-foreground checkbox-checkmark" />
206
+ )}
207
+ </div>
208
+ <span className="font-semibold text-sm">
209
+ {groupLabels[group as ProcessingGroup]}
210
+ </span>
211
+ </button>
212
+
213
+ <div className="pl-5 space-y-1">
214
+ {groupOptions.map((option) => (
215
+ <label key={option.id} className="flex items-center gap-2 cursor-pointer group">
216
+ <div className="relative">
217
+ <input
218
+ type="checkbox"
219
+ checked={option.enabled}
220
+ onChange={() => toggleOption(option.id)}
221
+ className="sr-only"
222
+ />
223
+ <div
224
+ className={cn(
225
+ 'w-4 h-4 rounded border-2 flex items-center justify-center transition-all',
226
+ option.enabled
227
+ ? 'bg-primary border-primary checkbox-checked'
228
+ : 'border-border group-hover:border-primary/50'
229
+ )}
230
+ >
231
+ {option.enabled && (
232
+ <motion.div
233
+ initial={{ scale: 0 }}
234
+ animate={{ scale: 1 }}
235
+ transition={{ type: 'spring', stiffness: 500, damping: 30 }}
236
+ >
237
+ <Check className="w-2.5 h-2.5 text-primary-foreground checkbox-checkmark" />
238
+ </motion.div>
239
+ )}
240
+ </div>
241
+ </div>
242
+ <span className="text-sm">{option.label}</span>
243
+ </label>
244
+ ))}
245
+ </div>
246
+ </div>
247
+ );
248
+ })}
249
+ </div>
250
+
251
+ {/* Right Column: Enabled Processing Descriptions */}
252
+ <div className="bg-muted/20 rounded-lg p-4 border border-border/50">
253
+ <h4 className="font-medium text-sm mb-3 text-muted-foreground">Enabled processing:</h4>
254
+
255
+ {hasEnabledOptions ? (
256
+ <div className="space-y-4">
257
+ {Object.entries(enabledByGroup).map(([group, groupOptions]) => (
258
+ <div key={group}>
259
+ <h5 className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-2">
260
+ {groupLabels[group as ProcessingGroup]}
261
+ </h5>
262
+ <ul className="space-y-1.5">
263
+ {groupOptions.map((option) => (
264
+ <motion.li
265
+ key={option.id}
266
+ initial={{ opacity: 0, x: -10 }}
267
+ animate={{ opacity: 1, x: 0 }}
268
+ exit={{ opacity: 0, x: -10 }}
269
+ className="flex items-start gap-2 text-sm"
270
+ >
271
+ <span className="text-primary mt-0.5">•</span>
272
+ <span>{optionDescriptions[option.id]}</span>
273
+ </motion.li>
274
+ ))}
275
+ </ul>
276
+ </div>
277
+ ))}
278
+ </div>
279
+ ) : (
280
+ <p className="text-sm text-muted-foreground italic">No processing options enabled</p>
281
+ )}
282
+ </div>
283
+ </div>
284
+
285
+ {/* DO NOT REMOVE - May utilize this in the future.
286
+ {onAutoAcceptRevisionsChange && (
287
+ <div className="pt-4 border-t border-border">
288
+ <RevisionHandlingOptions
289
+ autoAccept={autoAcceptRevisions}
290
+ onAutoAcceptChange={onAutoAcceptRevisionsChange}
291
+ />
292
+ </div>
293
+ )}
294
+ */}
295
+ </div>
296
+ );
297
+ }