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,396 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useEffect,
6
+ ReactNode,
7
+ useCallback,
8
+ useMemo,
9
+ useRef,
10
+ } from 'react';
11
+ import {
12
+ GlobalStats,
13
+ GlobalStatsContextType,
14
+ StatsUpdate,
15
+ DailyStats,
16
+ WeeklyStats,
17
+ MonthlyStats,
18
+ createDefaultGlobalStats,
19
+ createEmptyDailyStats,
20
+ createEmptyWeeklyStats,
21
+ createEmptyMonthlyStats,
22
+ } from '@/types/globalStats';
23
+ import { loadGlobalStats, saveGlobalStats, resetGlobalStats } from '@/utils/indexedDB';
24
+ import { logger } from '@/utils/logger';
25
+
26
+ const GlobalStatsContext = createContext<GlobalStatsContextType | undefined>(undefined);
27
+
28
+ /**
29
+ * Formats a date to YYYY-MM-DD string in local timezone.
30
+ * Using local timezone prevents off-by-one errors that occur when using
31
+ * toISOString() which uses UTC (e.g., 11pm local could be next day in UTC).
32
+ */
33
+ const formatLocalDate = (date: Date): string => {
34
+ const year = date.getFullYear();
35
+ const month = String(date.getMonth() + 1).padStart(2, '0');
36
+ const day = String(date.getDate()).padStart(2, '0');
37
+ return `${year}-${month}-${day}`;
38
+ };
39
+
40
+ /**
41
+ * Formats a date to YYYY-MM string in local timezone.
42
+ */
43
+ const formatLocalMonth = (date: Date): string => {
44
+ const year = date.getFullYear();
45
+ const month = String(date.getMonth() + 1).padStart(2, '0');
46
+ return `${year}-${month}`;
47
+ };
48
+
49
+ export function GlobalStatsProvider({ children }: { children: ReactNode }) {
50
+ const log = logger.namespace('GlobalStats');
51
+ const [stats, setStats] = useState<GlobalStats>(createDefaultGlobalStats());
52
+ const [isLoading, setIsLoading] = useState(true);
53
+
54
+ // RACE CONDITION FIX: Debounce save operations to prevent concurrent writes
55
+ // When updateStats is called rapidly, we only want to save once after updates settle
56
+ const saveTimerRef = useRef<NodeJS.Timeout | null>(null);
57
+ const latestStatsRef = useRef<GlobalStats>(stats);
58
+
59
+ // Keep latestStatsRef updated for debounced saves
60
+ useEffect(() => {
61
+ latestStatsRef.current = stats;
62
+ }, [stats]);
63
+
64
+ // Initialize GlobalStats - Load from IndexedDB using connection pool
65
+ useEffect(() => {
66
+ let isMounted = true;
67
+
68
+ const initStats = async () => {
69
+ try {
70
+ // Load existing stats using connection pool
71
+ const existingStats = await loadGlobalStats();
72
+
73
+ // Only update state if component is still mounted
74
+ if (!isMounted) return;
75
+
76
+ if (existingStats) {
77
+ // Check if we need to roll over to new day/week/month
78
+ const updatedStats = checkAndRollOverPeriods(existingStats);
79
+ setStats(updatedStats);
80
+
81
+ // Save rolled-over stats if changed
82
+ if (updatedStats !== existingStats) {
83
+ await saveGlobalStats(updatedStats);
84
+ }
85
+ } else {
86
+ // No existing stats, save defaults
87
+ const defaultStats = createDefaultGlobalStats();
88
+ await saveGlobalStats(defaultStats);
89
+ setStats(defaultStats);
90
+ }
91
+ } catch (error) {
92
+ if (isMounted) {
93
+ log.error('Failed to initialize GlobalStats:', error);
94
+ }
95
+ } finally {
96
+ if (isMounted) {
97
+ setIsLoading(false);
98
+ }
99
+ }
100
+ };
101
+
102
+ initStats();
103
+
104
+ // Cleanup: mark component as unmounted (connection pool handles db cleanup)
105
+ return () => {
106
+ isMounted = false;
107
+ // Clear any pending debounced save to prevent memory leaks
108
+ if (saveTimerRef.current) {
109
+ clearTimeout(saveTimerRef.current);
110
+ }
111
+ };
112
+ }, []); // Empty deps = runs once on mount, cleanup on unmount
113
+
114
+ // Check if we need to roll over to new day/week/month
115
+ const checkAndRollOverPeriods = (currentStats: GlobalStats): GlobalStats => {
116
+ const now = new Date();
117
+ // Use local timezone to prevent off-by-one errors
118
+ const today = formatLocalDate(now);
119
+ const currentMonth = formatLocalMonth(now);
120
+
121
+ let updated = { ...currentStats };
122
+
123
+ // Check if new day
124
+ if (updated.today.date !== today) {
125
+ // Archive yesterday's stats
126
+ updated.dailyHistory = [updated.today, ...updated.dailyHistory].slice(0, 30);
127
+
128
+ // Create new today
129
+ updated.today = createEmptyDailyStats(today);
130
+ }
131
+
132
+ // Check if new week (use local timezone for consistency)
133
+ const monday = formatLocalDate(getMonday(now));
134
+ if (updated.currentWeek.weekStart !== monday) {
135
+ // Archive last week's stats
136
+ updated.weeklyHistory = [updated.currentWeek, ...updated.weeklyHistory].slice(0, 12);
137
+
138
+ // Create new week
139
+ const sunday = formatLocalDate(getSunday(now));
140
+ updated.currentWeek = createEmptyWeeklyStats(monday, sunday);
141
+ }
142
+
143
+ // Check if new month
144
+ if (updated.currentMonth.month !== currentMonth) {
145
+ // Archive last month's stats
146
+ updated.monthlyHistory = [updated.currentMonth, ...updated.monthlyHistory].slice(0, 12);
147
+
148
+ // Create new month
149
+ updated.currentMonth = createEmptyMonthlyStats(currentMonth);
150
+ }
151
+
152
+ return updated;
153
+ };
154
+
155
+ // Update stats
156
+ const updateStats = useCallback(
157
+ async (update: StatsUpdate) => {
158
+ setStats((prevStats) => {
159
+ const updatedStats = { ...prevStats };
160
+
161
+ // Update all-time totals
162
+ if (update.documentsProcessed) {
163
+ updatedStats.allTime.documentsProcessed += update.documentsProcessed;
164
+ }
165
+ if (update.hyperlinksChecked) {
166
+ updatedStats.allTime.hyperlinksChecked += update.hyperlinksChecked;
167
+ }
168
+ if (update.feedbackImported) {
169
+ updatedStats.allTime.feedbackImported += update.feedbackImported;
170
+ }
171
+ if (update.timeSaved) {
172
+ updatedStats.allTime.timeSaved += update.timeSaved;
173
+ }
174
+
175
+ // Set first/last activity dates
176
+ const now = new Date().toISOString();
177
+ if (!updatedStats.allTime.firstActivityDate) {
178
+ updatedStats.allTime.firstActivityDate = now;
179
+ }
180
+ updatedStats.allTime.lastActivityDate = now;
181
+
182
+ // Update today's stats
183
+ if (update.documentsProcessed) {
184
+ updatedStats.today.documentsProcessed += update.documentsProcessed;
185
+ }
186
+ if (update.hyperlinksChecked) {
187
+ updatedStats.today.hyperlinksChecked += update.hyperlinksChecked;
188
+ }
189
+ if (update.feedbackImported) {
190
+ updatedStats.today.feedbackImported += update.feedbackImported;
191
+ }
192
+ if (update.timeSaved) {
193
+ updatedStats.today.timeSaved += update.timeSaved;
194
+ }
195
+
196
+ // Update current week stats
197
+ if (update.documentsProcessed) {
198
+ updatedStats.currentWeek.documentsProcessed += update.documentsProcessed;
199
+ }
200
+ if (update.hyperlinksChecked) {
201
+ updatedStats.currentWeek.hyperlinksChecked += update.hyperlinksChecked;
202
+ }
203
+ if (update.feedbackImported) {
204
+ updatedStats.currentWeek.feedbackImported += update.feedbackImported;
205
+ }
206
+ if (update.timeSaved) {
207
+ updatedStats.currentWeek.timeSaved += update.timeSaved;
208
+ }
209
+
210
+ // Update current month stats
211
+ if (update.documentsProcessed) {
212
+ updatedStats.currentMonth.documentsProcessed += update.documentsProcessed;
213
+ }
214
+ if (update.hyperlinksChecked) {
215
+ updatedStats.currentMonth.hyperlinksChecked += update.hyperlinksChecked;
216
+ }
217
+ if (update.feedbackImported) {
218
+ updatedStats.currentMonth.feedbackImported += update.feedbackImported;
219
+ }
220
+ if (update.timeSaved) {
221
+ updatedStats.currentMonth.timeSaved += update.timeSaved;
222
+ }
223
+
224
+ updatedStats.lastUpdated = now;
225
+
226
+ // DEBOUNCED SAVE: Clear any pending save and schedule a new one
227
+ // This prevents race conditions when updateStats is called rapidly
228
+ if (saveTimerRef.current) {
229
+ clearTimeout(saveTimerRef.current);
230
+ }
231
+
232
+ saveTimerRef.current = setTimeout(() => {
233
+ // Use latestStatsRef to ensure we save the most recent state
234
+ saveGlobalStats(latestStatsRef.current).catch((error: Error) =>
235
+ log.error('Failed to save stats:', error)
236
+ );
237
+ }, 1000); // 1 second debounce
238
+
239
+ return updatedStats;
240
+ });
241
+ },
242
+ [log]
243
+ );
244
+
245
+ // Get methods
246
+ const getTodayStats = useCallback((): DailyStats => stats.today, [stats]);
247
+ const getWeekStats = useCallback((): WeeklyStats => stats.currentWeek, [stats]);
248
+ const getMonthStats = useCallback((): MonthlyStats => stats.currentMonth, [stats]);
249
+
250
+ const getDailyHistory = useCallback(
251
+ (days: number = 30): DailyStats[] => {
252
+ return [stats.today, ...stats.dailyHistory].slice(0, days);
253
+ },
254
+ [stats]
255
+ );
256
+
257
+ const getWeeklyHistory = useCallback(
258
+ (weeks: number = 12): WeeklyStats[] => {
259
+ return [stats.currentWeek, ...stats.weeklyHistory].slice(0, weeks);
260
+ },
261
+ [stats]
262
+ );
263
+
264
+ const getMonthlyHistory = useCallback(
265
+ (months: number = 12): MonthlyStats[] => {
266
+ return [stats.currentMonth, ...stats.monthlyHistory].slice(0, months);
267
+ },
268
+ [stats]
269
+ );
270
+
271
+ // Comparison methods
272
+ const getTodayChange = useCallback((): StatsUpdate => {
273
+ const yesterday = stats.dailyHistory[0];
274
+ if (!yesterday) {
275
+ return {
276
+ documentsProcessed: stats.today.documentsProcessed,
277
+ hyperlinksChecked: stats.today.hyperlinksChecked,
278
+ feedbackImported: stats.today.feedbackImported,
279
+ timeSaved: stats.today.timeSaved,
280
+ };
281
+ }
282
+
283
+ return {
284
+ documentsProcessed: stats.today.documentsProcessed - yesterday.documentsProcessed,
285
+ hyperlinksChecked: stats.today.hyperlinksChecked - yesterday.hyperlinksChecked,
286
+ feedbackImported: stats.today.feedbackImported - yesterday.feedbackImported,
287
+ timeSaved: stats.today.timeSaved - yesterday.timeSaved,
288
+ };
289
+ }, [stats]);
290
+
291
+ const getWeekChange = useCallback((): StatsUpdate => {
292
+ const lastWeek = stats.weeklyHistory[0];
293
+ if (!lastWeek) {
294
+ return {
295
+ documentsProcessed: stats.currentWeek.documentsProcessed,
296
+ hyperlinksChecked: stats.currentWeek.hyperlinksChecked,
297
+ feedbackImported: stats.currentWeek.feedbackImported,
298
+ timeSaved: stats.currentWeek.timeSaved,
299
+ };
300
+ }
301
+
302
+ return {
303
+ documentsProcessed: stats.currentWeek.documentsProcessed - lastWeek.documentsProcessed,
304
+ hyperlinksChecked: stats.currentWeek.hyperlinksChecked - lastWeek.hyperlinksChecked,
305
+ feedbackImported: stats.currentWeek.feedbackImported - lastWeek.feedbackImported,
306
+ timeSaved: stats.currentWeek.timeSaved - lastWeek.timeSaved,
307
+ };
308
+ }, [stats]);
309
+
310
+ const getMonthChange = useCallback((): StatsUpdate => {
311
+ const lastMonth = stats.monthlyHistory[0];
312
+ if (!lastMonth) {
313
+ return {
314
+ documentsProcessed: stats.currentMonth.documentsProcessed,
315
+ hyperlinksChecked: stats.currentMonth.hyperlinksChecked,
316
+ feedbackImported: stats.currentMonth.feedbackImported,
317
+ timeSaved: stats.currentMonth.timeSaved,
318
+ };
319
+ }
320
+
321
+ return {
322
+ documentsProcessed: stats.currentMonth.documentsProcessed - lastMonth.documentsProcessed,
323
+ hyperlinksChecked: stats.currentMonth.hyperlinksChecked - lastMonth.hyperlinksChecked,
324
+ feedbackImported: stats.currentMonth.feedbackImported - lastMonth.feedbackImported,
325
+ timeSaved: stats.currentMonth.timeSaved - lastMonth.timeSaved,
326
+ };
327
+ }, [stats]);
328
+
329
+ // Reset all stats
330
+ const resetAllStats = useCallback(async () => {
331
+ const freshStats = createDefaultGlobalStats();
332
+ setStats(freshStats);
333
+ await resetGlobalStats(freshStats);
334
+ }, []);
335
+
336
+ // PERFORMANCE FIX: Memoize provider value to prevent unnecessary re-renders
337
+ // This prevents all consumers (Dashboard, Analytics, etc.) from re-rendering
338
+ // on every stats update when the methods haven't changed
339
+ const contextValue = useMemo(
340
+ () => ({
341
+ stats,
342
+ updateStats,
343
+ getTodayStats,
344
+ getWeekStats,
345
+ getMonthStats,
346
+ getDailyHistory,
347
+ getWeeklyHistory,
348
+ getMonthlyHistory,
349
+ getTodayChange,
350
+ getWeekChange,
351
+ getMonthChange,
352
+ resetAllStats,
353
+ isLoading,
354
+ }),
355
+ [
356
+ stats,
357
+ updateStats,
358
+ getTodayStats,
359
+ getWeekStats,
360
+ getMonthStats,
361
+ getDailyHistory,
362
+ getWeeklyHistory,
363
+ getMonthlyHistory,
364
+ getTodayChange,
365
+ getWeekChange,
366
+ getMonthChange,
367
+ resetAllStats,
368
+ isLoading,
369
+ ]
370
+ );
371
+
372
+ return <GlobalStatsContext.Provider value={contextValue}>{children}</GlobalStatsContext.Provider>;
373
+ }
374
+
375
+ export function useGlobalStats() {
376
+ const context = useContext(GlobalStatsContext);
377
+ if (context === undefined) {
378
+ throw new Error('useGlobalStats must be used within a GlobalStatsProvider');
379
+ }
380
+ return context;
381
+ }
382
+
383
+ // Helper functions
384
+ function getMonday(date: Date): Date {
385
+ const d = new Date(date);
386
+ const day = d.getDay();
387
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1);
388
+ return new Date(d.setDate(diff));
389
+ }
390
+
391
+ function getSunday(date: Date): Date {
392
+ const monday = getMonday(date);
393
+ const sunday = new Date(monday);
394
+ sunday.setDate(monday.getDate() + 6);
395
+ return sunday;
396
+ }