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,408 @@
1
+ /**
2
+ * ChangeItem - Individual change display component
3
+ *
4
+ * Displays a single unified change with source badge, description,
5
+ * optional diff view, and location context.
6
+ */
7
+
8
+ import type { UnifiedChange } from '@/types/session';
9
+ import { cn } from '@/utils/cn';
10
+ import { FileText, User } from 'lucide-react';
11
+
12
+ interface ChangeItemProps {
13
+ change: UnifiedChange;
14
+ }
15
+
16
+ export function ChangeItem({ change }: ChangeItemProps) {
17
+ const hasContent = change.before || change.after;
18
+ const hasDiff = change.before && change.after && change.before !== change.after;
19
+ const isHyperlinkChange = change.category === 'hyperlink' && change.hyperlinkChange;
20
+ const hasPropertyChange = change.propertyChange && change.propertyChange.property;
21
+ const hasGroupedProperties = change.groupedProperties && change.groupedProperties.length > 0;
22
+
23
+ // Detect if this is a combined "Updated" change (has both before and after)
24
+ const isUpdatedChange = hasDiff || change.description?.toLowerCase().startsWith('updated');
25
+
26
+ return (
27
+ <div className="group p-3 bg-muted/30 rounded-md hover:bg-muted/50 transition-colors">
28
+ {/* Header Row */}
29
+ <div className="flex items-start justify-between gap-2">
30
+ <div className="flex items-start gap-2 flex-1 min-w-0">
31
+ {/* Source Badge */}
32
+ <SourceBadge source={change.source} />
33
+
34
+ {/* Description */}
35
+ <div className="flex-1 min-w-0">
36
+ <p className="text-sm text-foreground break-words">
37
+ {change.description || 'Change applied'}
38
+ {/* Show Content ID in title for hyperlink changes */}
39
+ {isHyperlinkChange && change.hyperlinkChange?.contentId && (
40
+ <code className="ml-2 bg-primary/10 text-primary px-1.5 py-0.5 rounded text-xs font-medium">
41
+ {extractContentIdSuffix(change.hyperlinkChange.contentId)}
42
+ </code>
43
+ )}
44
+ </p>
45
+
46
+ {/* Affected Text - Show context of what was changed (5-8 words) */}
47
+ {change.affectedText && !change.hyperlinkChange && !isUpdatedChange && (
48
+ <p className="text-xs text-muted-foreground mt-1" title={change.affectedText}>
49
+ Text: "{getContextWords(change.affectedText)}"
50
+ </p>
51
+ )}
52
+
53
+ {/* Author (for Word revisions) */}
54
+ {change.author && (
55
+ <div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
56
+ <User className="w-3 h-3" />
57
+ <span>{change.author}</span>
58
+ {change.date && (
59
+ <>
60
+ <span className="mx-1">-</span>
61
+ <span>{formatDate(change.date)}</span>
62
+ </>
63
+ )}
64
+ </div>
65
+ )}
66
+
67
+ {/* Location Context */}
68
+ {change.location?.nearestHeading && (
69
+ <div className="flex items-center gap-1 mt-1 text-xs text-muted-foreground">
70
+ <FileText className="w-3 h-3" />
71
+ <span>Near: {change.location.nearestHeading}</span>
72
+ </div>
73
+ )}
74
+ </div>
75
+ </div>
76
+
77
+ {/* Count Badge (for consolidated changes) */}
78
+ {change.count && change.count > 1 && (
79
+ <span className="shrink-0 px-2 py-0.5 text-xs font-medium bg-primary/10 text-primary rounded-full">
80
+ {change.count} instances
81
+ </span>
82
+ )}
83
+ </div>
84
+
85
+ {/* Hyperlink Change View */}
86
+ {isHyperlinkChange ? (
87
+ <HyperlinkChangeView change={change.hyperlinkChange!} />
88
+ ) : hasContent ? (
89
+ <div className="mt-2 pl-7">
90
+ {hasDiff ? (
91
+ <UpdatedDiffView before={change.before!} after={change.after!} />
92
+ ) : change.before ? (
93
+ <div className="text-xs">
94
+ <span className="text-muted-foreground">Removed: </span>
95
+ <span className="text-red-600 dark:text-red-400 line-through">
96
+ "{getContextWords(change.before)}"
97
+ </span>
98
+ </div>
99
+ ) : change.after ? (
100
+ <div className="text-xs">
101
+ <span className="text-muted-foreground">Added: </span>
102
+ <span className="text-green-600 dark:text-green-400">
103
+ "{getContextWords(change.after)}"
104
+ </span>
105
+ </div>
106
+ ) : null}
107
+ </div>
108
+ ) : null}
109
+
110
+ {/* Property Change View (for single formatting change) */}
111
+ {hasPropertyChange && !hasGroupedProperties && (
112
+ <div className="mt-2 pl-7 text-xs">
113
+ <span className="text-muted-foreground">Property: </span>
114
+ <span className="font-medium">{change.propertyChange!.property}</span>
115
+ {change.affectedText && (
116
+ <span className="text-muted-foreground ml-1" title={change.affectedText}>
117
+ on "{truncateText(change.affectedText, 30)}"
118
+ </span>
119
+ )}
120
+ {(change.propertyChange!.oldValue || change.propertyChange!.newValue) && (
121
+ <span className="ml-2">
122
+ {change.propertyChange!.oldValue && (
123
+ <span className="text-red-600 dark:text-red-400 line-through mr-1">
124
+ {formatPropertyValue(change.propertyChange!.oldValue)}
125
+ </span>
126
+ )}
127
+ <span className="text-muted-foreground mx-1">-&gt;</span>
128
+ {change.propertyChange!.newValue && (
129
+ <span className="text-green-600 dark:text-green-400">
130
+ {formatPropertyValue(change.propertyChange!.newValue)}
131
+ </span>
132
+ )}
133
+ </span>
134
+ )}
135
+ </div>
136
+ )}
137
+
138
+ {/* Grouped Property Changes View (multiple formatting changes on same text) */}
139
+ {hasGroupedProperties && (
140
+ <div className="mt-2 pl-7">
141
+ <div className="text-xs text-muted-foreground mb-1">Properties changed:</div>
142
+ <div className="space-y-1">
143
+ {change.groupedProperties!.map((prop, idx) => (
144
+ <div key={idx} className="text-xs flex items-center gap-2">
145
+ <span className="text-muted-foreground">-</span>
146
+ <span className="font-medium min-w-[100px]">{prop.property}:</span>
147
+ {prop.oldValue && (
148
+ <span className="text-red-600 dark:text-red-400 line-through">
149
+ {formatPropertyValue(prop.oldValue)}
150
+ </span>
151
+ )}
152
+ <span className="text-muted-foreground">-&gt;</span>
153
+ {prop.newValue && (
154
+ <span className="text-green-600 dark:text-green-400">
155
+ {formatPropertyValue(prop.newValue)}
156
+ </span>
157
+ )}
158
+ </div>
159
+ ))}
160
+ </div>
161
+ </div>
162
+ )}
163
+ </div>
164
+ );
165
+ }
166
+
167
+ // Sub-components
168
+
169
+ interface SourceBadgeProps {
170
+ source: 'word' | 'processing';
171
+ }
172
+
173
+ function SourceBadge({ source }: SourceBadgeProps) {
174
+ const isWord = source === 'word';
175
+
176
+ return (
177
+ <span
178
+ className={cn(
179
+ 'shrink-0 px-1.5 py-0.5 text-[10px] font-medium rounded uppercase tracking-wide',
180
+ isWord
181
+ ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300'
182
+ : 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300'
183
+ )}
184
+ >
185
+ {isWord ? 'Word' : 'DocHub'}
186
+ </span>
187
+ );
188
+ }
189
+
190
+ interface UpdatedDiffViewProps {
191
+ before: string;
192
+ after: string;
193
+ }
194
+
195
+ /**
196
+ * Shows "Updated" change with before/after in a clean format
197
+ */
198
+ function UpdatedDiffView({ before, after }: UpdatedDiffViewProps) {
199
+ const beforeContext = getContextWords(before);
200
+ const afterContext = getContextWords(after);
201
+
202
+ return (
203
+ <div className="space-y-1.5 text-xs">
204
+ <div className="flex items-start gap-2">
205
+ <span className="text-muted-foreground shrink-0">From:</span>
206
+ <span className="text-red-600 dark:text-red-400 line-through break-words">
207
+ "{beforeContext}"
208
+ </span>
209
+ </div>
210
+ <div className="flex items-start gap-2">
211
+ <span className="text-muted-foreground shrink-0">To:</span>
212
+ <span className="text-green-600 dark:text-green-400 break-words">
213
+ "{afterContext}"
214
+ </span>
215
+ </div>
216
+ </div>
217
+ );
218
+ }
219
+
220
+ interface HyperlinkChangeViewProps {
221
+ change: {
222
+ urlBefore?: string;
223
+ urlAfter?: string;
224
+ textBefore?: string;
225
+ textAfter?: string;
226
+ status?: 'updated' | 'not_found' | 'expired';
227
+ contentId?: string;
228
+ };
229
+ }
230
+
231
+ function HyperlinkChangeView({ change }: HyperlinkChangeViewProps) {
232
+ const urlChanged = change.urlBefore !== change.urlAfter;
233
+ const textChanged = change.textBefore !== change.textAfter;
234
+
235
+ return (
236
+ <div className="mt-2 pl-7 space-y-3">
237
+ {/* Content ID - Always shown prominently at top for identification */}
238
+ {change.contentId && (
239
+ <div className="flex items-center gap-2">
240
+ <span className="text-xs font-medium text-muted-foreground">Content ID:</span>
241
+ <code className="bg-primary/10 text-primary px-1.5 py-0.5 rounded text-xs font-medium">
242
+ {change.contentId}
243
+ </code>
244
+ </div>
245
+ )}
246
+
247
+ {/* Status Badge (for not_found or expired) */}
248
+ {change.status && change.status !== 'updated' && (
249
+ <div className="flex items-center gap-2">
250
+ <span
251
+ className={cn(
252
+ 'px-2 py-0.5 text-xs font-medium rounded uppercase',
253
+ change.status === 'not_found'
254
+ ? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300'
255
+ : 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-300'
256
+ )}
257
+ >
258
+ {change.status === 'not_found' ? 'Source Not Found' : 'Source Expired'}
259
+ </span>
260
+ </div>
261
+ )}
262
+
263
+ {/* What Changed section */}
264
+ {(urlChanged || textChanged) && (
265
+ <div className="space-y-2">
266
+ <span className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
267
+ What Changed:
268
+ </span>
269
+
270
+ {/* URL Change */}
271
+ {urlChanged && (
272
+ <div className="space-y-1 ml-2">
273
+ <span className="text-xs text-muted-foreground">URL:</span>
274
+ <div className="space-y-1 text-xs font-mono">
275
+ {change.urlBefore && (
276
+ <div className="flex items-start gap-2">
277
+ <span className="shrink-0 w-1 h-full bg-red-500 rounded-full" />
278
+ <span className="text-red-600 dark:text-red-400 line-through break-all">
279
+ {truncateUrl(change.urlBefore)}
280
+ </span>
281
+ </div>
282
+ )}
283
+ {change.urlAfter && (
284
+ <div className="flex items-start gap-2">
285
+ <span className="shrink-0 w-1 h-full bg-green-500 rounded-full" />
286
+ <span className="text-green-600 dark:text-green-400 break-all">
287
+ {truncateUrl(change.urlAfter)}
288
+ </span>
289
+ </div>
290
+ )}
291
+ </div>
292
+ </div>
293
+ )}
294
+
295
+ {/* Text to Display Change */}
296
+ {textChanged && (
297
+ <div className="space-y-1 ml-2">
298
+ <span className="text-xs text-muted-foreground">Text to Display:</span>
299
+ <div className="space-y-1 text-xs font-mono">
300
+ {change.textBefore && (
301
+ <div className="flex items-start gap-2">
302
+ <span className="shrink-0 w-1 h-full bg-red-500 rounded-full" />
303
+ <span className="text-red-600 dark:text-red-400 line-through break-all">
304
+ {truncateText(change.textBefore, 80)}
305
+ </span>
306
+ </div>
307
+ )}
308
+ {change.textAfter && (
309
+ <div className="flex items-start gap-2">
310
+ <span className="shrink-0 w-1 h-full bg-green-500 rounded-full" />
311
+ <span className="text-green-600 dark:text-green-400 break-all">
312
+ {truncateText(change.textAfter, 80)}
313
+ </span>
314
+ </div>
315
+ )}
316
+ </div>
317
+ </div>
318
+ )}
319
+ </div>
320
+ )}
321
+ </div>
322
+ );
323
+ }
324
+
325
+ // Utility functions
326
+
327
+ function formatDate(date: Date | string): string {
328
+ const d = typeof date === 'string' ? new Date(date) : date;
329
+ return d.toLocaleDateString(undefined, {
330
+ month: 'short',
331
+ day: 'numeric',
332
+ year: 'numeric',
333
+ });
334
+ }
335
+
336
+ function truncateText(text: string, maxLength: number): string {
337
+ if (text.length <= maxLength) return text;
338
+ return text.substring(0, maxLength) + '...';
339
+ }
340
+
341
+ /**
342
+ * Gets a context snippet (5-8 words) from text for display
343
+ * Provides meaningful context without overwhelming the UI
344
+ */
345
+ function getContextWords(text: string): string {
346
+ if (!text) return '';
347
+
348
+ // Clean up whitespace
349
+ const cleaned = text.replace(/\s+/g, ' ').trim();
350
+ if (!cleaned) return '';
351
+
352
+ // Split into words
353
+ const words = cleaned.split(' ').filter((w) => w.length > 0);
354
+ if (words.length === 0) return '';
355
+
356
+ // If 8 words or less, return as-is
357
+ if (words.length <= 8) {
358
+ return cleaned;
359
+ }
360
+
361
+ // Take first 6 words and add ellipsis
362
+ return words.slice(0, 6).join(' ') + '...';
363
+ }
364
+
365
+ function truncateUrl(url: string, maxLength: number = 60): string {
366
+ if (url.length <= maxLength) return url;
367
+ try {
368
+ const parsed = new URL(url);
369
+ const domain = parsed.hostname;
370
+ const remaining = maxLength - domain.length - 5;
371
+ return `${domain}/...${url.slice(-Math.max(10, remaining))}`;
372
+ } catch {
373
+ return url.substring(0, maxLength) + '...';
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Extract the numeric suffix from a Content ID for compact display
379
+ * e.g., "TSRC-ABC-123456" -> "123456"
380
+ */
381
+ function extractContentIdSuffix(contentId: string): string {
382
+ // Try to extract the last numeric/alphanumeric segment
383
+ const parts = contentId.split(/[-_]/);
384
+ const lastPart = parts[parts.length - 1];
385
+ if (lastPart && lastPart.length > 0) {
386
+ return lastPart;
387
+ }
388
+ // Fallback to last 6 characters if no delimiter found
389
+ return contentId.slice(-6);
390
+ }
391
+
392
+ /**
393
+ * Format property values for display, handling objects that would show as [object Object]
394
+ */
395
+ function formatPropertyValue(value: unknown): string {
396
+ if (value === null || value === undefined) return '';
397
+ if (typeof value === 'object') {
398
+ try {
399
+ // For complex objects, show a simplified JSON representation
400
+ const json = JSON.stringify(value);
401
+ // Truncate if too long
402
+ return json.length > 50 ? json.substring(0, 47) + '...' : json;
403
+ } catch {
404
+ return '[complex value]';
405
+ }
406
+ }
407
+ return String(value);
408
+ }