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,530 @@
1
+ import type { DocumentChange } from '@/types/session';
2
+ import { cn } from '@/utils/cn';
3
+ import { motion } from 'framer-motion';
4
+ import {
5
+ AlertTriangle,
6
+ CheckCircle,
7
+ ChevronDown,
8
+ ChevronRight,
9
+ FileText,
10
+ Hash,
11
+ Link,
12
+ List,
13
+ XCircle,
14
+ } from 'lucide-react';
15
+ import { useState } from 'react';
16
+
17
+ interface TrackedChangesDetailProps {
18
+ changes: DocumentChange[];
19
+ className?: string;
20
+ }
21
+
22
+ interface GroupedChange {
23
+ category: string;
24
+ title: string;
25
+ icon: typeof Link;
26
+ iconColor: string;
27
+ items: DocumentChange[];
28
+ summary?: string;
29
+ }
30
+
31
+ export function TrackedChangesDetail({ changes, className }: TrackedChangesDetailProps) {
32
+ const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
33
+
34
+ const toggleCategory = (category: string) => {
35
+ setExpandedCategories((prev) => {
36
+ const next = new Set(prev);
37
+ if (next.has(category)) {
38
+ next.delete(category);
39
+ } else {
40
+ next.add(category);
41
+ }
42
+ return next;
43
+ });
44
+ };
45
+
46
+ // Group changes by category with intelligent aggregation
47
+ const groupedChanges = groupChangesByCategory(changes);
48
+
49
+ if (groupedChanges.length === 0) {
50
+ return (
51
+ <div className={cn('text-center py-4 text-sm text-muted-foreground', className)}>
52
+ No tracked changes to display
53
+ </div>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <div className={cn('space-y-3', className)}>
59
+ <div className="flex items-center justify-between mb-2">
60
+ <h4 className="font-semibold text-sm">Tracked Changes ({changes.length})</h4>
61
+ <button
62
+ onClick={() => {
63
+ if (expandedCategories.size === groupedChanges.length) {
64
+ setExpandedCategories(new Set());
65
+ } else {
66
+ setExpandedCategories(new Set(groupedChanges.map((g) => g.category)));
67
+ }
68
+ }}
69
+ className="text-xs text-muted-foreground hover:text-foreground"
70
+ >
71
+ {expandedCategories.size === groupedChanges.length ? 'Collapse All' : 'Expand All'}
72
+ </button>
73
+ </div>
74
+
75
+ <div className="space-y-2">
76
+ {groupedChanges.map((group) => (
77
+ <ChangeGroup
78
+ key={group.category}
79
+ group={group}
80
+ isExpanded={expandedCategories.has(group.category)}
81
+ onToggle={() => toggleCategory(group.category)}
82
+ />
83
+ ))}
84
+ </div>
85
+ </div>
86
+ );
87
+ }
88
+
89
+ function ChangeGroup({
90
+ group,
91
+ isExpanded,
92
+ onToggle,
93
+ }: {
94
+ group: GroupedChange;
95
+ isExpanded: boolean;
96
+ onToggle: () => void;
97
+ }) {
98
+ const Icon = group.icon;
99
+
100
+ return (
101
+ <motion.div
102
+ initial={{ opacity: 0, y: 10 }}
103
+ animate={{ opacity: 1, y: 0 }}
104
+ className="border border-border rounded-lg overflow-hidden bg-muted/20"
105
+ >
106
+ <button
107
+ onClick={onToggle}
108
+ className="w-full flex items-center gap-3 p-3 hover:bg-muted/40 transition-colors text-left"
109
+ >
110
+ <Icon className={cn('w-5 h-5 shrink-0', group.iconColor)} />
111
+ <div className="flex-1 min-w-0">
112
+ <div className="flex items-center gap-2">
113
+ <span className="font-medium text-sm">{group.title}</span>
114
+ <span className="text-xs text-muted-foreground">({group.items.length})</span>
115
+ </div>
116
+ {group.summary && (
117
+ <p className="text-xs text-muted-foreground mt-0.5 truncate">{group.summary}</p>
118
+ )}
119
+ </div>
120
+ {isExpanded ? (
121
+ <ChevronDown className="w-4 h-4 text-muted-foreground shrink-0" />
122
+ ) : (
123
+ <ChevronRight className="w-4 h-4 text-muted-foreground shrink-0" />
124
+ )}
125
+ </button>
126
+
127
+ {isExpanded && (
128
+ <motion.div
129
+ initial={{ height: 0, opacity: 0 }}
130
+ animate={{ height: 'auto', opacity: 1 }}
131
+ exit={{ height: 0, opacity: 0 }}
132
+ className="border-t border-border"
133
+ >
134
+ {group.category === 'hyperlink_update' ? (
135
+ <HyperlinkUpdatesView items={group.items} />
136
+ ) : group.category === 'hyperlink_failed' ? (
137
+ <HyperlinkFailedView items={group.items} />
138
+ ) : group.category === 'list_fix' ? (
139
+ <ListFixesView items={group.items} />
140
+ ) : group.category === 'blank_lines' ? (
141
+ <BlankLinesView items={group.items} />
142
+ ) : (
143
+ <DefaultChangesView items={group.items} />
144
+ )}
145
+ </motion.div>
146
+ )}
147
+ </motion.div>
148
+ );
149
+ }
150
+
151
+ function HyperlinkUpdatesView({ items }: { items: DocumentChange[] }) {
152
+ // Group by Header 2
153
+ const byHeader2 = groupByHeader2(items);
154
+
155
+ return (
156
+ <div className="p-3 space-y-4 max-h-96 overflow-y-auto">
157
+ {byHeader2.map((section, idx) => (
158
+ <div key={idx} className="space-y-2">
159
+ {section.header && (
160
+ <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
161
+ <FileText className="w-3 h-3" />
162
+ <span>Under "{section.header}"</span>
163
+ </div>
164
+ )}
165
+ <div className="space-y-3 pl-5">
166
+ {section.items.map((change, itemIdx) => (
167
+ <div key={itemIdx} className="space-y-1.5 text-xs border-l-2 border-primary/30 pl-3">
168
+ {/* Content ID - Always shown prominently at top for identification */}
169
+ {change.contentId && (
170
+ <div className="flex items-center gap-2">
171
+ <span className="text-muted-foreground text-[10px]">Content ID:</span>
172
+ <code className="bg-primary/10 text-primary px-1.5 py-0.5 rounded text-[10px] font-medium">
173
+ {change.contentId}
174
+ </code>
175
+ </div>
176
+ )}
177
+
178
+ {/* What Changed section */}
179
+ <div className="space-y-1">
180
+ <span className="text-[10px] font-medium text-muted-foreground uppercase tracking-wide">
181
+ What Changed:
182
+ </span>
183
+
184
+ {/* URL change (if URL changed) */}
185
+ {change.urlBefore && change.urlAfter && (
186
+ <div className="space-y-0.5 ml-2">
187
+ <span className="text-[10px] text-muted-foreground">URL:</span>
188
+ <div className="flex items-start gap-2">
189
+ <span className="text-muted-foreground min-w-[45px]">Before:</span>
190
+ <code className="bg-red-500/10 text-red-600 dark:text-red-400 px-1.5 py-0.5 rounded break-all flex-1 line-through">
191
+ {change.urlBefore}
192
+ </code>
193
+ </div>
194
+ <div className="flex items-start gap-2">
195
+ <span className="text-muted-foreground min-w-[45px]">After:</span>
196
+ <code className="bg-green-500/10 text-green-600 dark:text-green-400 px-1.5 py-0.5 rounded break-all flex-1">
197
+ {change.urlAfter}
198
+ </code>
199
+ </div>
200
+ </div>
201
+ )}
202
+
203
+ {/* Text to Display change (if text changed) */}
204
+ {change.before && change.after && (
205
+ <div className="space-y-0.5 ml-2">
206
+ <span className="text-[10px] text-muted-foreground">Text to Display:</span>
207
+ <div className="flex items-start gap-2">
208
+ <span className="text-muted-foreground min-w-[45px]">Before:</span>
209
+ <code className="bg-red-500/10 text-red-600 dark:text-red-400 px-1.5 py-0.5 rounded break-all flex-1 line-through">
210
+ {change.before}
211
+ </code>
212
+ </div>
213
+ <div className="flex items-start gap-2">
214
+ <span className="text-muted-foreground min-w-[45px]">After:</span>
215
+ <code className="bg-green-500/10 text-green-600 dark:text-green-400 px-1.5 py-0.5 rounded break-all flex-1">
216
+ {change.after}
217
+ </code>
218
+ </div>
219
+ </div>
220
+ )}
221
+ </div>
222
+ </div>
223
+ ))}
224
+ </div>
225
+ </div>
226
+ ))}
227
+ </div>
228
+ );
229
+ }
230
+
231
+ function HyperlinkFailedView({ items }: { items: DocumentChange[] }) {
232
+ const byHeader2 = groupByHeader2(items);
233
+
234
+ return (
235
+ <div className="p-3 space-y-4 max-h-96 overflow-y-auto">
236
+ {byHeader2.map((section, idx) => (
237
+ <div key={idx} className="space-y-2">
238
+ {section.header && (
239
+ <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
240
+ <FileText className="w-3 h-3" />
241
+ <span>Under "{section.header}"</span>
242
+ </div>
243
+ )}
244
+ <div className="space-y-1.5 pl-5">
245
+ {section.items.map((change, itemIdx) => (
246
+ <div key={itemIdx} className="flex items-start gap-2 text-xs">
247
+ {change.hyperlinkStatus === 'expired' ? (
248
+ <AlertTriangle className="w-4 h-4 text-orange-500 shrink-0 mt-0.5" />
249
+ ) : (
250
+ <XCircle className="w-4 h-4 text-red-500 shrink-0 mt-0.5" />
251
+ )}
252
+ <div className="flex-1">
253
+ <span className="font-medium">{change.description}</span>
254
+ {change.contentId && (
255
+ <span className="ml-2 text-muted-foreground">({change.contentId})</span>
256
+ )}
257
+ <span
258
+ className={cn(
259
+ 'ml-2 text-[10px] px-1.5 py-0.5 rounded',
260
+ change.hyperlinkStatus === 'expired'
261
+ ? 'bg-orange-500/10 text-orange-600'
262
+ : 'bg-red-500/10 text-red-600'
263
+ )}
264
+ >
265
+ {change.hyperlinkStatus === 'expired' ? 'Expired' : 'Not Found'}
266
+ </span>
267
+ </div>
268
+ </div>
269
+ ))}
270
+ </div>
271
+ </div>
272
+ ))}
273
+ </div>
274
+ );
275
+ }
276
+
277
+ function ListFixesView({ items }: { items: DocumentChange[] }) {
278
+ const byHeader2 = groupByHeader2(items);
279
+
280
+ return (
281
+ <div className="p-3 space-y-4 max-h-96 overflow-y-auto">
282
+ {byHeader2.map((section, idx) => (
283
+ <div key={idx} className="space-y-2">
284
+ {section.header && (
285
+ <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
286
+ <FileText className="w-3 h-3" />
287
+ <span>Under "{section.header}"</span>
288
+ </div>
289
+ )}
290
+ <div className="space-y-1 pl-5">
291
+ {section.items.map((change, itemIdx) => (
292
+ <div key={itemIdx} className="flex items-start gap-2 text-xs">
293
+ <CheckCircle className="w-4 h-4 text-green-500 shrink-0 mt-0.5" />
294
+ <span>{change.description}</span>
295
+ {change.count !== undefined && change.count > 0 && (
296
+ <span className="text-muted-foreground">({change.count} items)</span>
297
+ )}
298
+ </div>
299
+ ))}
300
+ </div>
301
+ </div>
302
+ ))}
303
+ </div>
304
+ );
305
+ }
306
+
307
+ function BlankLinesView({ items }: { items: DocumentChange[] }) {
308
+ // Aggregate blank line changes
309
+ const removed = items.filter((c) => c.description.toLowerCase().includes('removed'));
310
+ const added = items.filter((c) => c.description.toLowerCase().includes('added'));
311
+
312
+ const totalRemoved = removed.reduce((sum, c) => sum + (c.count || 1), 0);
313
+ const totalAdded = added.reduce((sum, c) => sum + (c.count || 1), 0);
314
+
315
+ return (
316
+ <div className="p-3 space-y-2 text-xs">
317
+ {totalRemoved > 0 && (
318
+ <div className="flex items-center gap-2">
319
+ <XCircle className="w-4 h-4 text-red-500" />
320
+ <span>Removed {totalRemoved} blank line{totalRemoved !== 1 ? 's' : ''}</span>
321
+ </div>
322
+ )}
323
+ {totalAdded > 0 && (
324
+ <div className="flex items-center gap-2">
325
+ <CheckCircle className="w-4 h-4 text-green-500" />
326
+ <span>
327
+ Added {totalAdded} standardized line{totalAdded !== 1 ? 's' : ''} for structure
328
+ </span>
329
+ </div>
330
+ )}
331
+ </div>
332
+ );
333
+ }
334
+
335
+ function DefaultChangesView({ items }: { items: DocumentChange[] }) {
336
+ return (
337
+ <div className="p-3 space-y-1.5 max-h-96 overflow-y-auto">
338
+ {items.map((change, idx) => (
339
+ <div key={idx} className="flex items-start gap-2 text-xs">
340
+ <CheckCircle className="w-4 h-4 text-green-500 shrink-0 mt-0.5" />
341
+ <div className="flex-1">
342
+ <p className="font-medium">{change.description}</p>
343
+ {change.nearestHeader2 && (
344
+ <p className="text-muted-foreground text-[10px] mt-0.5">
345
+ Under "{change.nearestHeader2}"
346
+ </p>
347
+ )}
348
+ {change.before && change.after && (
349
+ <div className="mt-1 space-y-0.5">
350
+ <div className="flex items-start gap-2">
351
+ <span className="text-muted-foreground min-w-[45px]">Before:</span>
352
+ <code className="bg-muted px-1.5 py-0.5 rounded break-all flex-1">
353
+ {change.before}
354
+ </code>
355
+ </div>
356
+ <div className="flex items-start gap-2">
357
+ <span className="text-muted-foreground min-w-[45px]">After:</span>
358
+ <code className="bg-primary/10 text-primary px-1.5 py-0.5 rounded break-all flex-1">
359
+ {change.after}
360
+ </code>
361
+ </div>
362
+ </div>
363
+ )}
364
+ {change.count !== undefined && change.count > 0 && (
365
+ <p className="text-muted-foreground text-[10px] mt-0.5">
366
+ {change.count} occurrence{change.count !== 1 ? 's' : ''}
367
+ </p>
368
+ )}
369
+ </div>
370
+ </div>
371
+ ))}
372
+ </div>
373
+ );
374
+ }
375
+
376
+ // Helper: Group changes by category
377
+ function groupChangesByCategory(changes: DocumentChange[]): GroupedChange[] {
378
+ const groups: GroupedChange[] = [];
379
+
380
+ // Separate changes by category
381
+ const hyperlinksUpdated = changes.filter(
382
+ (c) => c.category === 'hyperlink_update' && c.hyperlinkStatus === 'updated'
383
+ );
384
+ const hyperlinksFailed = changes.filter(
385
+ (c) =>
386
+ c.category === 'hyperlink_failed' ||
387
+ c.hyperlinkStatus === 'expired' ||
388
+ c.hyperlinkStatus === 'not_found'
389
+ );
390
+ const listFixes = changes.filter((c) => c.category === 'list_fix');
391
+ const blankLines = changes.filter((c) => c.category === 'blank_lines');
392
+ const styleChanges = changes.filter((c) => c.category === 'style_application');
393
+ const structureChanges = changes.filter((c) => c.category === 'structure');
394
+ const otherChanges = changes.filter(
395
+ (c) =>
396
+ !c.category ||
397
+ (c.category === 'other' &&
398
+ !hyperlinksUpdated.includes(c) &&
399
+ !hyperlinksFailed.includes(c) &&
400
+ !listFixes.includes(c) &&
401
+ !blankLines.includes(c) &&
402
+ !styleChanges.includes(c) &&
403
+ !structureChanges.includes(c))
404
+ );
405
+
406
+ // Hyperlink updates
407
+ if (hyperlinksUpdated.length > 0) {
408
+ groups.push({
409
+ category: 'hyperlink_update',
410
+ title: 'Updated Hyperlink URLs / Content IDs',
411
+ icon: Link,
412
+ iconColor: 'text-blue-500',
413
+ items: hyperlinksUpdated,
414
+ summary: `${hyperlinksUpdated.length} hyperlink${hyperlinksUpdated.length !== 1 ? 's' : ''} updated`,
415
+ });
416
+ }
417
+
418
+ // Failed/Expired hyperlinks
419
+ if (hyperlinksFailed.length > 0) {
420
+ const expiredCount = hyperlinksFailed.filter((c) => c.hyperlinkStatus === 'expired').length;
421
+ const notFoundCount = hyperlinksFailed.filter(
422
+ (c) => c.hyperlinkStatus === 'not_found'
423
+ ).length;
424
+
425
+ let summary = '';
426
+ if (expiredCount > 0 && notFoundCount > 0) {
427
+ summary = `${expiredCount} expired, ${notFoundCount} not found`;
428
+ } else if (expiredCount > 0) {
429
+ summary = `${expiredCount} expired`;
430
+ } else {
431
+ summary = `${notFoundCount} not found`;
432
+ }
433
+
434
+ groups.push({
435
+ category: 'hyperlink_failed',
436
+ title: 'Hyperlink Issues Found',
437
+ icon: AlertTriangle,
438
+ iconColor: 'text-orange-500',
439
+ items: hyperlinksFailed,
440
+ summary,
441
+ });
442
+ }
443
+
444
+ // List formatting fixes
445
+ if (listFixes.length > 0) {
446
+ const totalListsFixed = listFixes.reduce((sum, c) => sum + (c.count || 1), 0);
447
+ groups.push({
448
+ category: 'list_fix',
449
+ title: 'List Formatting',
450
+ icon: List,
451
+ iconColor: 'text-purple-500',
452
+ items: listFixes,
453
+ summary: `Fixed ${totalListsFixed} list${totalListsFixed !== 1 ? 's' : ''}`,
454
+ });
455
+ }
456
+
457
+ // Blank lines (condensed)
458
+ if (blankLines.length > 0) {
459
+ groups.push({
460
+ category: 'blank_lines',
461
+ title: 'Structure Changes',
462
+ icon: FileText,
463
+ iconColor: 'text-gray-500',
464
+ items: blankLines,
465
+ summary: 'Blank line adjustments',
466
+ });
467
+ }
468
+
469
+ // Style applications
470
+ if (styleChanges.length > 0) {
471
+ groups.push({
472
+ category: 'style_application',
473
+ title: 'Style Applications',
474
+ icon: Hash,
475
+ iconColor: 'text-indigo-500',
476
+ items: styleChanges,
477
+ summary: `${styleChanges.length} style${styleChanges.length !== 1 ? 's' : ''} applied`,
478
+ });
479
+ }
480
+
481
+ // Other structure changes
482
+ if (structureChanges.length > 0) {
483
+ groups.push({
484
+ category: 'structure',
485
+ title: 'Other Structure Changes',
486
+ icon: FileText,
487
+ iconColor: 'text-teal-500',
488
+ items: structureChanges,
489
+ summary: `${structureChanges.length} change${structureChanges.length !== 1 ? 's' : ''}`,
490
+ });
491
+ }
492
+
493
+ // Other changes
494
+ if (otherChanges.length > 0) {
495
+ groups.push({
496
+ category: 'other',
497
+ title: 'Other Changes',
498
+ icon: FileText,
499
+ iconColor: 'text-gray-500',
500
+ items: otherChanges,
501
+ summary: `${otherChanges.length} miscellaneous change${otherChanges.length !== 1 ? 's' : ''}`,
502
+ });
503
+ }
504
+
505
+ return groups;
506
+ }
507
+
508
+ // Helper: Group items by nearest Header 2
509
+ function groupByHeader2(
510
+ items: DocumentChange[]
511
+ ): Array<{ header: string | null; items: DocumentChange[] }> {
512
+ const grouped = new Map<string | null, DocumentChange[]>();
513
+
514
+ for (const item of items) {
515
+ const header = item.nearestHeader2 || null;
516
+ if (!grouped.has(header)) {
517
+ grouped.set(header, []);
518
+ }
519
+ grouped.get(header)!.push(item);
520
+ }
521
+
522
+ // Convert to array, sort by header (nulls last)
523
+ return Array.from(grouped.entries())
524
+ .sort(([a], [b]) => {
525
+ if (a === null) return 1;
526
+ if (b === null) return -1;
527
+ return a.localeCompare(b);
528
+ })
529
+ .map(([header, items]) => ({ header, items }));
530
+ }