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,546 @@
1
+ /**
2
+ * TrackedChangesPanel - Container for tracked changes views
3
+ *
4
+ * Provides tabbed interface for:
5
+ * - Inline view: Word-like tracked changes display
6
+ * - List view: Categorized list of changes (existing ChangeViewer)
7
+ * - Comparison view: Side-by-side pre vs post processing diff
8
+ */
9
+
10
+ import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
11
+ import { motion, AnimatePresence } from 'framer-motion';
12
+ import {
13
+ FileText,
14
+ List,
15
+ Columns,
16
+ ChevronDown,
17
+ ChevronUp,
18
+ ExternalLink,
19
+ Maximize2,
20
+ ChevronLeft,
21
+ ChevronRight,
22
+ RefreshCw,
23
+ ChevronsUpDown,
24
+ ChevronsDownUp,
25
+ Keyboard,
26
+ } from 'lucide-react';
27
+ import { cn } from '@/utils/cn';
28
+ import type { TrackedChangesViewMode, TrackedChangesPanelProps } from '@/types/editor';
29
+ import type { ChangeEntry, UnifiedChange } from '@/types/session';
30
+ import { DocumentSnapshotService } from '@/services/document/DocumentSnapshotService';
31
+ import { InlineChangesView } from './InlineChangesView';
32
+ import { SideBySideDiff } from '../comparison/SideBySideDiff';
33
+
34
+ interface TrackedChangesPanelFullProps extends TrackedChangesPanelProps {
35
+ /** Changes from docxmlater tracked changes */
36
+ wordRevisions?: ChangeEntry[];
37
+ /** Changes from DocHub processing */
38
+ processingChanges?: ChangeEntry[];
39
+ /** Post-processing paragraph text (for comparison) */
40
+ postProcessingText?: string[];
41
+ /** Whether panel is expanded */
42
+ defaultExpanded?: boolean;
43
+ /** Callback when editor is opened */
44
+ onOpenEditor: () => void;
45
+ }
46
+
47
+ /**
48
+ * Tab button component
49
+ */
50
+ function TabButton({
51
+ active,
52
+ onClick,
53
+ icon: Icon,
54
+ label,
55
+ count,
56
+ }: {
57
+ active: boolean;
58
+ onClick: () => void;
59
+ icon: React.ElementType;
60
+ label: string;
61
+ count?: number;
62
+ }) {
63
+ return (
64
+ <button
65
+ onClick={onClick}
66
+ className={cn(
67
+ 'flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg transition-colors',
68
+ active
69
+ ? 'bg-primary text-primary-foreground'
70
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'
71
+ )}
72
+ >
73
+ <Icon className="w-4 h-4" />
74
+ <span>{label}</span>
75
+ {count !== undefined && count > 0 && (
76
+ <span
77
+ className={cn(
78
+ 'px-1.5 py-0.5 text-xs rounded-full',
79
+ active
80
+ ? 'bg-primary-foreground/20 text-primary-foreground'
81
+ : 'bg-muted-foreground/20 text-muted-foreground'
82
+ )}
83
+ >
84
+ {count}
85
+ </span>
86
+ )}
87
+ </button>
88
+ );
89
+ }
90
+
91
+
92
+ /**
93
+ * Simple list view placeholder (existing ChangeViewer logic)
94
+ */
95
+ function ListViewContent({ changes }: { changes: ChangeEntry[] }) {
96
+ const grouped = useMemo(() => {
97
+ const groups: Record<string, ChangeEntry[]> = {};
98
+ for (const change of changes) {
99
+ const category = change.category || 'other';
100
+ if (!groups[category]) {
101
+ groups[category] = [];
102
+ }
103
+ groups[category].push(change);
104
+ }
105
+ return groups;
106
+ }, [changes]);
107
+
108
+ const categoryLabels: Record<string, string> = {
109
+ content: 'Content Changes',
110
+ formatting: 'Formatting Changes',
111
+ structural: 'Structural Changes',
112
+ table: 'Table Changes',
113
+ hyperlink: 'Hyperlink Changes',
114
+ other: 'Other Changes',
115
+ };
116
+
117
+ const categoryColors: Record<string, string> = {
118
+ content: 'border-l-blue-500',
119
+ formatting: 'border-l-purple-500',
120
+ structural: 'border-l-orange-500',
121
+ table: 'border-l-cyan-500',
122
+ hyperlink: 'border-l-green-500',
123
+ other: 'border-l-gray-500',
124
+ };
125
+
126
+ if (changes.length === 0) {
127
+ return (
128
+ <div className="p-8 text-center text-muted-foreground">
129
+ <List className="w-12 h-12 mx-auto mb-2 opacity-50" />
130
+ <p>No tracked changes</p>
131
+ </div>
132
+ );
133
+ }
134
+
135
+ return (
136
+ <div className="divide-y divide-border">
137
+ {Object.entries(grouped).map(([category, categoryChanges]) => (
138
+ <div key={category} className="p-4">
139
+ <h4 className="text-sm font-medium mb-3 flex items-center gap-2">
140
+ <span
141
+ className={cn(
142
+ 'w-2 h-2 rounded-full',
143
+ categoryColors[category]?.replace('border-l-', 'bg-') || 'bg-gray-500'
144
+ )}
145
+ />
146
+ {categoryLabels[category] || category} ({categoryChanges.length})
147
+ </h4>
148
+ <div className="space-y-2">
149
+ {categoryChanges.slice(0, 10).map((change, index) => (
150
+ <div
151
+ key={`${category}-${index}`}
152
+ className={cn(
153
+ 'p-3 bg-muted/50 rounded-lg border-l-2',
154
+ categoryColors[category] || 'border-l-gray-500'
155
+ )}
156
+ >
157
+ <p className="text-sm">{change.description}</p>
158
+ {change.content?.before && change.content?.after && (
159
+ <div className="mt-2 text-xs grid grid-cols-2 gap-2">
160
+ <div className="bg-red-50 dark:bg-red-900/20 p-2 rounded">
161
+ <span className="text-red-700 dark:text-red-300 line-through">
162
+ {change.content.before.slice(0, 100)}
163
+ {change.content.before.length > 100 && '...'}
164
+ </span>
165
+ </div>
166
+ <div className="bg-green-50 dark:bg-green-900/20 p-2 rounded">
167
+ <span className="text-green-700 dark:text-green-300">
168
+ {change.content.after.slice(0, 100)}
169
+ {change.content.after.length > 100 && '...'}
170
+ </span>
171
+ </div>
172
+ </div>
173
+ )}
174
+ <div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground">
175
+ {change.author && <span>By: {change.author}</span>}
176
+ {change.date && (
177
+ <span>{new Date(change.date).toLocaleDateString()}</span>
178
+ )}
179
+ </div>
180
+ </div>
181
+ ))}
182
+ {categoryChanges.length > 10 && (
183
+ <p className="text-xs text-muted-foreground text-center">
184
+ + {categoryChanges.length - 10} more changes
185
+ </p>
186
+ )}
187
+ </div>
188
+ </div>
189
+ ))}
190
+ </div>
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Main TrackedChangesPanel component
196
+ */
197
+ export function TrackedChangesPanel({
198
+ sessionId,
199
+ documentId,
200
+ wordRevisions = [],
201
+ processingChanges = [],
202
+ postProcessingText = [],
203
+ defaultExpanded = true,
204
+ onOpenEditor,
205
+ }: TrackedChangesPanelFullProps) {
206
+ const [activeTab, setActiveTab] = useState<TrackedChangesViewMode>('list');
207
+ const [isExpanded, setIsExpanded] = useState(defaultExpanded);
208
+ const [originalText, setOriginalText] = useState<string[]>([]);
209
+ const [isLoadingSnapshot, setIsLoadingSnapshot] = useState(false);
210
+ const [currentChangeIndex, setCurrentChangeIndex] = useState(0);
211
+ const [allParagraphsExpanded, setAllParagraphsExpanded] = useState(true);
212
+ const [showKeyboardHint, setShowKeyboardHint] = useState(false);
213
+
214
+ // Ref for keyboard focus
215
+ const panelRef = useRef<HTMLDivElement>(null);
216
+
217
+ // Combine all changes for total count
218
+ const allChanges = useMemo(() => {
219
+ return [...wordRevisions, ...processingChanges];
220
+ }, [wordRevisions, processingChanges]);
221
+
222
+ // Load snapshot for comparison view
223
+ useEffect(() => {
224
+ if (activeTab === 'comparison' && originalText.length === 0) {
225
+ setIsLoadingSnapshot(true);
226
+ DocumentSnapshotService.getSnapshot(sessionId, documentId)
227
+ .then((snapshot) => {
228
+ if (snapshot) {
229
+ setOriginalText(snapshot.textContent);
230
+ }
231
+ })
232
+ .catch((error) => {
233
+ console.error('Failed to load snapshot:', error);
234
+ })
235
+ .finally(() => {
236
+ setIsLoadingSnapshot(false);
237
+ });
238
+ }
239
+ }, [activeTab, sessionId, documentId, originalText.length]);
240
+
241
+ // Change navigation
242
+ const handlePreviousChange = useCallback(() => {
243
+ setCurrentChangeIndex((prev) => Math.max(0, prev - 1));
244
+ }, []);
245
+
246
+ const handleNextChange = useCallback(() => {
247
+ setCurrentChangeIndex((prev) => Math.min(allChanges.length - 1, prev + 1));
248
+ }, [allChanges.length]);
249
+
250
+ // Toggle all paragraphs expanded/collapsed
251
+ const toggleAllParagraphs = useCallback(() => {
252
+ setAllParagraphsExpanded((prev) => !prev);
253
+ }, []);
254
+
255
+ // Keyboard navigation handler
256
+ useEffect(() => {
257
+ const handleKeyDown = (e: KeyboardEvent) => {
258
+ // Only handle if panel is focused or contains focus
259
+ if (!panelRef.current?.contains(document.activeElement) && document.activeElement !== panelRef.current) {
260
+ return;
261
+ }
262
+
263
+ // Don't handle if user is typing in an input
264
+ if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
265
+ return;
266
+ }
267
+
268
+ switch (e.key) {
269
+ case 'j':
270
+ case 'ArrowDown':
271
+ if (!e.ctrlKey && !e.metaKey) {
272
+ e.preventDefault();
273
+ handleNextChange();
274
+ }
275
+ break;
276
+ case 'k':
277
+ case 'ArrowUp':
278
+ if (!e.ctrlKey && !e.metaKey) {
279
+ e.preventDefault();
280
+ handlePreviousChange();
281
+ }
282
+ break;
283
+ case 'e':
284
+ if (!e.ctrlKey && !e.metaKey) {
285
+ e.preventDefault();
286
+ toggleAllParagraphs();
287
+ }
288
+ break;
289
+ case '1':
290
+ if (!e.ctrlKey && !e.metaKey) {
291
+ e.preventDefault();
292
+ setActiveTab('inline');
293
+ }
294
+ break;
295
+ case '2':
296
+ if (!e.ctrlKey && !e.metaKey) {
297
+ e.preventDefault();
298
+ setActiveTab('list');
299
+ }
300
+ break;
301
+ case '3':
302
+ if (!e.ctrlKey && !e.metaKey) {
303
+ e.preventDefault();
304
+ setActiveTab('comparison');
305
+ }
306
+ break;
307
+ case '?':
308
+ e.preventDefault();
309
+ setShowKeyboardHint((prev) => !prev);
310
+ break;
311
+ case 'Escape':
312
+ setShowKeyboardHint(false);
313
+ break;
314
+ }
315
+ };
316
+
317
+ window.addEventListener('keydown', handleKeyDown);
318
+ return () => window.removeEventListener('keydown', handleKeyDown);
319
+ }, [handleNextChange, handlePreviousChange, toggleAllParagraphs]);
320
+
321
+ return (
322
+ <div
323
+ ref={panelRef}
324
+ tabIndex={0}
325
+ className="bg-card border border-border rounded-lg overflow-hidden focus:outline-none focus:ring-2 focus:ring-primary/50"
326
+ >
327
+ {/* Keyboard Shortcuts Hint Overlay */}
328
+ <AnimatePresence>
329
+ {showKeyboardHint && (
330
+ <motion.div
331
+ initial={{ opacity: 0 }}
332
+ animate={{ opacity: 1 }}
333
+ exit={{ opacity: 0 }}
334
+ className="absolute inset-0 z-50 bg-background/95 backdrop-blur-sm flex items-center justify-center"
335
+ onClick={() => setShowKeyboardHint(false)}
336
+ >
337
+ <div className="bg-card border border-border rounded-lg p-6 shadow-lg max-w-sm">
338
+ <h4 className="font-medium mb-4 flex items-center gap-2">
339
+ <Keyboard className="w-4 h-4" />
340
+ Keyboard Shortcuts
341
+ </h4>
342
+ <div className="space-y-2 text-sm">
343
+ <div className="flex justify-between gap-8">
344
+ <span className="text-muted-foreground">Next change</span>
345
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">j</kbd>
346
+ </div>
347
+ <div className="flex justify-between gap-8">
348
+ <span className="text-muted-foreground">Previous change</span>
349
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">k</kbd>
350
+ </div>
351
+ <div className="flex justify-between gap-8">
352
+ <span className="text-muted-foreground">Expand/Collapse all</span>
353
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">e</kbd>
354
+ </div>
355
+ <div className="flex justify-between gap-8">
356
+ <span className="text-muted-foreground">Inline view</span>
357
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">1</kbd>
358
+ </div>
359
+ <div className="flex justify-between gap-8">
360
+ <span className="text-muted-foreground">List view</span>
361
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">2</kbd>
362
+ </div>
363
+ <div className="flex justify-between gap-8">
364
+ <span className="text-muted-foreground">Comparison view</span>
365
+ <kbd className="px-2 py-0.5 bg-muted rounded text-xs font-mono">3</kbd>
366
+ </div>
367
+ </div>
368
+ <p className="text-xs text-muted-foreground mt-4">
369
+ Press <kbd className="px-1 bg-muted rounded">?</kbd> or <kbd className="px-1 bg-muted rounded">Esc</kbd> to close
370
+ </p>
371
+ </div>
372
+ </motion.div>
373
+ )}
374
+ </AnimatePresence>
375
+
376
+ {/* Header */}
377
+ <div
378
+ className="flex items-center justify-between px-4 py-3 bg-muted/30 cursor-pointer"
379
+ onClick={() => setIsExpanded(!isExpanded)}
380
+ >
381
+ <div className="flex items-center gap-3">
382
+ <FileText className="w-5 h-5 text-primary" />
383
+ <h3 className="font-medium">Tracked Changes</h3>
384
+ {allChanges.length > 0 && (
385
+ <span className="px-2 py-0.5 text-xs bg-primary/10 text-primary rounded-full">
386
+ {allChanges.length} changes
387
+ </span>
388
+ )}
389
+ </div>
390
+ <div className="flex items-center gap-2">
391
+ {/* Keyboard hint button */}
392
+ <button
393
+ onClick={(e) => {
394
+ e.stopPropagation();
395
+ setShowKeyboardHint(true);
396
+ }}
397
+ className="p-1.5 rounded-md hover:bg-muted transition-colors text-muted-foreground"
398
+ title="Keyboard shortcuts (?)"
399
+ >
400
+ <Keyboard className="w-4 h-4" />
401
+ </button>
402
+ <button
403
+ onClick={(e) => {
404
+ e.stopPropagation();
405
+ onOpenEditor();
406
+ }}
407
+ className="p-1.5 rounded-md hover:bg-muted transition-colors"
408
+ title="Open in Editor"
409
+ >
410
+ <Maximize2 className="w-4 h-4" />
411
+ </button>
412
+ {isExpanded ? (
413
+ <ChevronUp className="w-5 h-5 text-muted-foreground" />
414
+ ) : (
415
+ <ChevronDown className="w-5 h-5 text-muted-foreground" />
416
+ )}
417
+ </div>
418
+ </div>
419
+
420
+ {/* Content */}
421
+ <AnimatePresence>
422
+ {isExpanded && (
423
+ <motion.div
424
+ initial={{ height: 0, opacity: 0 }}
425
+ animate={{ height: 'auto', opacity: 1 }}
426
+ exit={{ height: 0, opacity: 0 }}
427
+ transition={{ duration: 0.2 }}
428
+ >
429
+ {/* Tab Bar */}
430
+ <div className="flex items-center justify-between px-4 py-2 border-b border-border bg-muted/10">
431
+ <div className="flex items-center gap-1">
432
+ <TabButton
433
+ active={activeTab === 'inline'}
434
+ onClick={() => setActiveTab('inline')}
435
+ icon={FileText}
436
+ label="Inline"
437
+ count={allChanges.length}
438
+ />
439
+ <TabButton
440
+ active={activeTab === 'list'}
441
+ onClick={() => setActiveTab('list')}
442
+ icon={List}
443
+ label="List"
444
+ count={allChanges.length}
445
+ />
446
+ <TabButton
447
+ active={activeTab === 'comparison'}
448
+ onClick={() => setActiveTab('comparison')}
449
+ icon={Columns}
450
+ label="Before/After"
451
+ />
452
+ </div>
453
+
454
+ {/* Controls */}
455
+ {allChanges.length > 0 && activeTab !== 'comparison' && (
456
+ <div className="flex items-center gap-2">
457
+ {/* Expand/Collapse All button (for inline view) */}
458
+ {activeTab === 'inline' && (
459
+ <button
460
+ onClick={toggleAllParagraphs}
461
+ className="p-1.5 rounded hover:bg-muted transition-colors flex items-center gap-1 text-xs text-muted-foreground"
462
+ title={allParagraphsExpanded ? 'Collapse all paragraphs (e)' : 'Expand all paragraphs (e)'}
463
+ >
464
+ {allParagraphsExpanded ? (
465
+ <ChevronsDownUp className="w-4 h-4" />
466
+ ) : (
467
+ <ChevronsUpDown className="w-4 h-4" />
468
+ )}
469
+ <span className="hidden sm:inline">
470
+ {allParagraphsExpanded ? 'Collapse' : 'Expand'}
471
+ </span>
472
+ </button>
473
+ )}
474
+
475
+ {/* Separator */}
476
+ {activeTab === 'inline' && (
477
+ <div className="w-px h-4 bg-border" />
478
+ )}
479
+
480
+ {/* Navigation controls */}
481
+ <div className="flex items-center gap-1">
482
+ <button
483
+ onClick={handlePreviousChange}
484
+ disabled={currentChangeIndex === 0}
485
+ className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
486
+ title="Previous change (k)"
487
+ >
488
+ <ChevronLeft className="w-4 h-4" />
489
+ </button>
490
+ <span className="text-xs text-muted-foreground px-2">
491
+ {currentChangeIndex + 1} / {allChanges.length}
492
+ </span>
493
+ <button
494
+ onClick={handleNextChange}
495
+ disabled={currentChangeIndex === allChanges.length - 1}
496
+ className="p-1 rounded hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed"
497
+ title="Next change (j)"
498
+ >
499
+ <ChevronRight className="w-4 h-4" />
500
+ </button>
501
+ </div>
502
+ </div>
503
+ )}
504
+ </div>
505
+
506
+ {/* Tab Content */}
507
+ <div className="max-h-[500px] overflow-y-auto">
508
+ {activeTab === 'inline' && (
509
+ <InlineChangesView
510
+ changes={allChanges}
511
+ highlightedChangeIndex={currentChangeIndex}
512
+ onChangeClick={(change, index) => setCurrentChangeIndex(index)}
513
+ allExpanded={allParagraphsExpanded}
514
+ />
515
+ )}
516
+
517
+ {activeTab === 'list' && <ListViewContent changes={allChanges} />}
518
+
519
+ {activeTab === 'comparison' && (
520
+ <>
521
+ {isLoadingSnapshot ? (
522
+ <div className="p-8 text-center text-muted-foreground">
523
+ <RefreshCw className="w-8 h-8 mx-auto mb-2 animate-spin" />
524
+ <p>Loading comparison data...</p>
525
+ </div>
526
+ ) : (
527
+ <SideBySideDiff
528
+ originalContent={originalText}
529
+ modifiedContent={postProcessingText}
530
+ syncScroll={true}
531
+ showLineNumbers={true}
532
+ collapseUnchanged={false}
533
+ height={400}
534
+ />
535
+ )}
536
+ </>
537
+ )}
538
+ </div>
539
+ </motion.div>
540
+ )}
541
+ </AnimatePresence>
542
+ </div>
543
+ );
544
+ }
545
+
546
+ export default TrackedChangesPanel;