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,187 @@
1
+ /**
2
+ * Memory Configuration for Electron Main Process
3
+ *
4
+ * Configures Node.js heap size and memory limits for document processing operations.
5
+ */
6
+
7
+ import { app } from 'electron';
8
+ import * as os from 'os';
9
+ import { logger } from '../src/utils/logger';
10
+
11
+ const log = logger.namespace('MemoryConfig');
12
+
13
+ export interface MemoryConfiguration {
14
+ heapSizeMB: number;
15
+ maxDocumentSizeMB: number;
16
+ recommendedConcurrency: number;
17
+ systemTotalMB: number;
18
+ systemFreeMB: number;
19
+ }
20
+
21
+ export class MemoryConfig {
22
+ private static readonly MIN_HEAP_SIZE_MB = 512; // Minimum 512MB
23
+ private static readonly DEFAULT_HEAP_SIZE_MB = 1024; // Default 1GB
24
+ private static readonly MAX_HEAP_SIZE_MB = 4096; // Maximum 4GB
25
+
26
+ /**
27
+ * Calculate optimal heap size based on system RAM
28
+ */
29
+ static calculateHeapSize(): number {
30
+ const totalMemoryMB = os.totalmem() / (1024 * 1024);
31
+ const freeMemoryMB = os.freemem() / (1024 * 1024);
32
+
33
+ log.info(`System RAM: ${totalMemoryMB.toFixed(0)}MB total, ${freeMemoryMB.toFixed(0)}MB free`);
34
+
35
+ // Use 25% of total RAM, but cap between MIN and MAX
36
+ let heapSize = Math.floor(totalMemoryMB * 0.25);
37
+
38
+ // Ensure within bounds
39
+ heapSize = Math.max(this.MIN_HEAP_SIZE_MB, heapSize);
40
+ heapSize = Math.min(this.MAX_HEAP_SIZE_MB, heapSize);
41
+
42
+ // Round to nearest 256MB for cleaner numbers
43
+ heapSize = Math.round(heapSize / 256) * 256;
44
+
45
+ log.info(`Calculated heap size: ${heapSize}MB`);
46
+ return heapSize;
47
+ }
48
+
49
+ /**
50
+ * Get memory configuration
51
+ */
52
+ static getConfiguration(): MemoryConfiguration {
53
+ const totalMemoryMB = os.totalmem() / (1024 * 1024);
54
+ const freeMemoryMB = os.freemem() / (1024 * 1024);
55
+ const heapSizeMB = this.calculateHeapSize();
56
+
57
+ // Max document size should be roughly 1/4 of heap size to allow for processing overhead
58
+ const maxDocumentSizeMB = Math.floor(heapSizeMB / 4);
59
+
60
+ // Recommended concurrency based on available heap
61
+ // Each concurrent operation needs ~256MB minimum
62
+ const recommendedConcurrency = Math.max(1, Math.floor(heapSizeMB / 256));
63
+
64
+ return {
65
+ heapSizeMB,
66
+ maxDocumentSizeMB,
67
+ recommendedConcurrency,
68
+ systemTotalMB: totalMemoryMB,
69
+ systemFreeMB: freeMemoryMB,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Configure Electron app with memory settings
75
+ * IMPORTANT: Must be called before app.ready
76
+ */
77
+ static configureApp(): void {
78
+ const config = this.getConfiguration();
79
+
80
+ log.info('========================================');
81
+ log.info('Configuration Summary:');
82
+ log.info(` System Total RAM: ${config.systemTotalMB.toFixed(0)}MB`);
83
+ log.info(` System Free RAM: ${config.systemFreeMB.toFixed(0)}MB`);
84
+ log.info(` Heap Size: ${config.heapSizeMB}MB`);
85
+ log.info(` Max Document Size: ${config.maxDocumentSizeMB}MB`);
86
+ log.info(` Recommended Concurrency: ${config.recommendedConcurrency}`);
87
+ log.info('========================================');
88
+
89
+ // Set heap size via V8 flags
90
+ // Note: These must be set before app initialization
91
+ try {
92
+ app.commandLine.appendSwitch('--js-flags', `--max-old-space-size=${config.heapSizeMB}`);
93
+ log.info(`✓ Set --max-old-space-size=${config.heapSizeMB}`);
94
+ } catch (error) {
95
+ log.error('✗ Failed to set heap size:', error);
96
+ }
97
+
98
+ // Enable GC exposure for manual garbage collection if needed
99
+ // Useful for cleaning up after large document operations
100
+ try {
101
+ app.commandLine.appendSwitch('--js-flags', '--expose-gc');
102
+ log.info('✓ Enabled garbage collection exposure');
103
+ } catch (error) {
104
+ log.warn('⚠️ Could not enable GC exposure:', error);
105
+ }
106
+
107
+ // Optimize garbage collection for document processing
108
+ // More aggressive GC to prevent memory buildup
109
+ try {
110
+ app.commandLine.appendSwitch('--js-flags', '--gc-interval=100');
111
+ log.info('✓ Configured aggressive garbage collection');
112
+ } catch (error) {
113
+ log.warn('⚠️ Could not configure GC interval:', error);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Log current memory usage
119
+ */
120
+ static logMemoryUsage(label: string = 'Current'): void {
121
+ const usage = process.memoryUsage();
122
+ const config = this.getConfiguration();
123
+
124
+ log.info(`${label} Memory Usage:`);
125
+ log.info(` Heap Used: ${(usage.heapUsed / 1024 / 1024).toFixed(2)}MB`);
126
+ log.info(` Heap Total: ${(usage.heapTotal / 1024 / 1024).toFixed(2)}MB`);
127
+ log.info(` Heap Limit: ~${config.heapSizeMB}MB`);
128
+ log.info(` External: ${(usage.external / 1024 / 1024).toFixed(2)}MB`);
129
+ log.info(` RSS: ${(usage.rss / 1024 / 1024).toFixed(2)}MB`);
130
+
131
+ const percentUsed = (usage.heapUsed / (config.heapSizeMB * 1024 * 1024)) * 100;
132
+ if (percentUsed > 85) {
133
+ log.error(`🚨 CRITICAL: ${percentUsed.toFixed(1)}% of heap used!`);
134
+ } else if (percentUsed > 70) {
135
+ log.warn(`⚠️ WARNING: ${percentUsed.toFixed(1)}% of heap used`);
136
+ } else {
137
+ log.info(`✓ ${percentUsed.toFixed(1)}% of heap used`);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Check if system has sufficient memory for operation
143
+ */
144
+ static canProcessDocument(documentSizeMB: number): boolean {
145
+ const config = this.getConfiguration();
146
+ const usage = process.memoryUsage();
147
+ const usedMB = usage.heapUsed / (1024 * 1024);
148
+ const availableMB = config.heapSizeMB - usedMB;
149
+
150
+ // Need at least 4x document size for processing overhead
151
+ const requiredMB = documentSizeMB * 4;
152
+
153
+ if (availableMB < requiredMB) {
154
+ log.error(`🚨 Insufficient memory to process ${documentSizeMB.toFixed(2)}MB document`);
155
+ log.error(` Required: ${requiredMB.toFixed(2)}MB, Available: ${availableMB.toFixed(2)}MB`);
156
+ return false;
157
+ }
158
+
159
+ log.info(`✓ Sufficient memory for ${documentSizeMB.toFixed(2)}MB document (${availableMB.toFixed(2)}MB available)`);
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Get memory statistics for reporting
165
+ */
166
+ static getStats(): {
167
+ heapUsedMB: number;
168
+ heapTotalMB: number;
169
+ heapLimitMB: number;
170
+ percentUsed: number;
171
+ systemFreeMB: number;
172
+ } {
173
+ const usage = process.memoryUsage();
174
+ const config = this.getConfiguration();
175
+ const heapUsedMB = usage.heapUsed / (1024 * 1024);
176
+
177
+ return {
178
+ heapUsedMB,
179
+ heapTotalMB: usage.heapTotal / (1024 * 1024),
180
+ heapLimitMB: config.heapSizeMB,
181
+ percentUsed: (heapUsedMB / config.heapSizeMB) * 100,
182
+ systemFreeMB: config.systemFreeMB,
183
+ };
184
+ }
185
+ }
186
+
187
+ export default MemoryConfig;
@@ -0,0 +1,394 @@
1
+ import { contextBridge, ipcRenderer, IpcRendererEvent, webUtils } from 'electron';
2
+ import type {
3
+ HyperlinkProcessingOptions,
4
+ BatchProcessingOptions,
5
+ BatchProgress,
6
+ } from '../src/types/hyperlink';
7
+ import type {
8
+ BackupCreateResponse,
9
+ BackupRestoreResponse,
10
+ BackupListResponse,
11
+ BackupDeleteResponse,
12
+ BackupCleanupResponse,
13
+ BackupVerifyResponse,
14
+ BackupStorageInfoResponse,
15
+ BackupSetConfigResponse,
16
+ BackupConfig,
17
+ } from '../src/types/backup';
18
+ import type {
19
+ SharePointConfig,
20
+ DictionarySyncStatus,
21
+ DictionarySyncResponse,
22
+ DictionaryInitResponse,
23
+ DictionaryCredentialsResponse,
24
+ SyncProgressUpdate,
25
+ } from '../src/types/dictionary';
26
+ import type { HyperlinkLookupResult } from './services/LocalDictionaryLookupService';
27
+
28
+ const electronAPI = {
29
+ minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
30
+ maximizeWindow: () => ipcRenderer.invoke('window-maximize'),
31
+ closeWindow: () => ipcRenderer.invoke('window-close'),
32
+ isMaximized: () => ipcRenderer.invoke('window-is-maximized'),
33
+ isFullscreen: () => ipcRenderer.invoke('window-is-fullscreen'),
34
+ getAppVersion: () => ipcRenderer.invoke('app-version'),
35
+ getPlatform: () => ipcRenderer.invoke('platform'),
36
+ openDevTools: () => ipcRenderer.invoke('open-dev-tools'),
37
+
38
+ // Always on top (pin window)
39
+ setAlwaysOnTop: (flag: boolean) => ipcRenderer.invoke('window-set-always-on-top', flag),
40
+ isAlwaysOnTop: () => ipcRenderer.invoke('window-is-always-on-top'),
41
+ onAlwaysOnTopChanged: (callback: (isOnTop: boolean) => void) => {
42
+ const subscription = (_event: IpcRendererEvent, isOnTop: boolean) => callback(isOnTop);
43
+ ipcRenderer.on('window-always-on-top-changed', subscription);
44
+ return () => ipcRenderer.removeListener('window-always-on-top-changed', subscription);
45
+ },
46
+
47
+ // File handling
48
+ selectDocuments: () => ipcRenderer.invoke('select-documents'),
49
+ processDocument: (path: string) => ipcRenderer.invoke('process-document', path),
50
+ showInFolder: (path: string) => ipcRenderer.invoke('show-in-folder', path),
51
+ openDocument: (path: string) => ipcRenderer.invoke('open-document', path),
52
+ getFileStats: (filePath: string) => ipcRenderer.invoke('get-file-stats', filePath),
53
+ restoreFromBackup: (backupPath: string, targetPath: string) =>
54
+ ipcRenderer.invoke('restore-from-backup', { backupPath, targetPath }),
55
+
56
+ getPathsForFiles: (files: File[]) => {
57
+ return files.map((file) => webUtils.getPathForFile(file));
58
+ },
59
+
60
+ // Export and Reporting
61
+ selectFolder: () => ipcRenderer.invoke('select-folder') as Promise<string | null>,
62
+ copyFilesToFolder: (filePaths: string[], destinationFolder: string) =>
63
+ ipcRenderer.invoke('copy-files-to-folder', { filePaths, destinationFolder }) as Promise<{
64
+ copied: number;
65
+ skipped: number;
66
+ }>,
67
+ getDownloadsPath: () => ipcRenderer.invoke('get-downloads-path') as Promise<string>,
68
+ createFolder: (folderPath: string) => ipcRenderer.invoke('create-folder', folderPath) as Promise<boolean>,
69
+ copyFileToFolder: (sourcePath: string, destFolder: string) =>
70
+ ipcRenderer.invoke('copy-file-to-folder', { sourcePath, destFolder }) as Promise<boolean>,
71
+ createReportZip: (folderPath: string, zipName: string) =>
72
+ ipcRenderer.invoke('create-report-zip', { folderPath, zipName }) as Promise<string>,
73
+ openOutlookEmail: (subject: string, attachmentPath: string) =>
74
+ ipcRenderer.invoke('open-outlook-email', { subject, attachmentPath }) as Promise<{
75
+ success: boolean;
76
+ method: 'outlook' | 'mailto';
77
+ }>,
78
+
79
+ // Document text extraction (for comparison views)
80
+ extractDocumentText: (filePath: string) =>
81
+ ipcRenderer.invoke('document:extract-text', filePath),
82
+
83
+ // Read file as buffer (for snapshot capture)
84
+ readFileAsBuffer: (filePath: string): Promise<ArrayBuffer> =>
85
+ ipcRenderer.invoke('file:read-buffer', filePath),
86
+
87
+ // Hyperlink processing
88
+ selectFiles: () => ipcRenderer.invoke('hyperlink:select-files'),
89
+ processHyperlinkDocument: (filePath: string, options: HyperlinkProcessingOptions) =>
90
+ ipcRenderer.invoke('hyperlink:process-document', { filePath, options }),
91
+ batchProcessDocuments: (filePaths: string[], options: BatchProcessingOptions) =>
92
+ ipcRenderer.invoke('hyperlink:batch-process', { filePaths, options }),
93
+ validateApi: (apiUrl: string) => ipcRenderer.invoke('hyperlink:validate-api', { apiUrl }),
94
+ callPowerAutomateApi: (
95
+ apiUrl: string,
96
+ payload: {
97
+ Lookup_ID: string[];
98
+ Hyperlinks_Checked: number;
99
+ Total_Hyperlinks: number;
100
+ First_Name: string;
101
+ Last_Name: string;
102
+ Email: string;
103
+ },
104
+ timeout?: number
105
+ ) => ipcRenderer.invoke('hyperlink:call-api', { apiUrl, payload, timeout }),
106
+ cancelOperation: (operationId: string) =>
107
+ ipcRenderer.invoke('hyperlink:cancel-operation', { operationId }),
108
+ onBatchProgress: (callback: (progress: BatchProgress) => void) => {
109
+ const subscription = (_event: IpcRendererEvent, progress: BatchProgress) => callback(progress);
110
+ ipcRenderer.on('hyperlink:batch-progress', subscription);
111
+ return () => ipcRenderer.removeListener('hyperlink:batch-progress', subscription);
112
+ },
113
+
114
+ onWindowMaximized: (callback: () => void) => {
115
+ const subscription = (_event: IpcRendererEvent) => callback();
116
+ ipcRenderer.on('window-maximized', subscription);
117
+ return () => ipcRenderer.removeListener('window-maximized', subscription);
118
+ },
119
+
120
+ onWindowUnmaximized: (callback: () => void) => {
121
+ const subscription = (_event: IpcRendererEvent) => callback();
122
+ ipcRenderer.on('window-unmaximized', subscription);
123
+ return () => ipcRenderer.removeListener('window-unmaximized', subscription);
124
+ },
125
+
126
+ onWindowFullscreen: (callback: () => void) => {
127
+ const subscription = (_event: IpcRendererEvent) => callback();
128
+ ipcRenderer.on('window-fullscreen', subscription);
129
+ return () => ipcRenderer.removeListener('window-fullscreen', subscription);
130
+ },
131
+
132
+ onWindowUnfullscreen: (callback: () => void) => {
133
+ const subscription = (_event: IpcRendererEvent) => callback();
134
+ ipcRenderer.on('window-unfullscreen', subscription);
135
+ return () => ipcRenderer.removeListener('window-unfullscreen', subscription);
136
+ },
137
+
138
+ // Export/Import
139
+ exportSettings: () => ipcRenderer.invoke('export-settings'),
140
+ importSettings: () => ipcRenderer.invoke('import-settings'),
141
+ saveExportData: (filePath: string, data: any) =>
142
+ ipcRenderer.invoke('save-export-data', { filePath, data }),
143
+
144
+ // Backup operations
145
+ backup: {
146
+ create: (documentPath: string): Promise<BackupCreateResponse> =>
147
+ ipcRenderer.invoke('backup:create', documentPath),
148
+ restore: (backupPath: string, targetPath: string): Promise<BackupRestoreResponse> =>
149
+ ipcRenderer.invoke('backup:restore', { backupPath, targetPath }),
150
+ list: (documentPath: string): Promise<BackupListResponse> =>
151
+ ipcRenderer.invoke('backup:list', documentPath),
152
+ delete: (backupPath: string): Promise<BackupDeleteResponse> =>
153
+ ipcRenderer.invoke('backup:delete', backupPath),
154
+ cleanup: (documentPath: string): Promise<BackupCleanupResponse> =>
155
+ ipcRenderer.invoke('backup:cleanup', documentPath),
156
+ cleanupAll: (): Promise<BackupCleanupResponse> =>
157
+ ipcRenderer.invoke('backup:cleanup-all'),
158
+ verify: (backupPath: string): Promise<BackupVerifyResponse> =>
159
+ ipcRenderer.invoke('backup:verify', backupPath),
160
+ getStorageInfo: (): Promise<BackupStorageInfoResponse> =>
161
+ ipcRenderer.invoke('backup:storage-info'),
162
+ setConfig: (config: Partial<BackupConfig>): Promise<BackupSetConfigResponse> =>
163
+ ipcRenderer.invoke('backup:set-config', config),
164
+ },
165
+
166
+ // Dictionary operations (Local SharePoint Dictionary)
167
+ dictionary: {
168
+ initialize: (): Promise<DictionaryInitResponse> =>
169
+ ipcRenderer.invoke('dictionary:initialize'),
170
+ configureSync: (config: SharePointConfig): Promise<{ success: boolean; error?: string }> =>
171
+ ipcRenderer.invoke('dictionary:configure-sync', config),
172
+ setCredentials: (clientSecret: string): Promise<DictionaryCredentialsResponse> =>
173
+ ipcRenderer.invoke('dictionary:set-credentials', clientSecret),
174
+ sync: (): Promise<DictionarySyncResponse> =>
175
+ ipcRenderer.invoke('dictionary:sync'),
176
+ startScheduler: (intervalHours: number): Promise<{ success: boolean; error?: string }> =>
177
+ ipcRenderer.invoke('dictionary:start-scheduler', intervalHours),
178
+ stopScheduler: (): Promise<{ success: boolean; error?: string }> =>
179
+ ipcRenderer.invoke('dictionary:stop-scheduler'),
180
+ lookup: (lookupId: string): Promise<{ success: boolean; result?: HyperlinkLookupResult; error?: string }> =>
181
+ ipcRenderer.invoke('dictionary:lookup', lookupId),
182
+ batchLookup: (lookupIds: string[]): Promise<{ success: boolean; results?: HyperlinkLookupResult[]; error?: string }> =>
183
+ ipcRenderer.invoke('dictionary:batch-lookup', lookupIds),
184
+ getStatus: (): Promise<{ success: boolean; status?: DictionarySyncStatus; error?: string }> =>
185
+ ipcRenderer.invoke('dictionary:get-status'),
186
+ // Interactive SharePoint retrieval (using browser login)
187
+ retrieveFromSharePoint: (fileUrl: string): Promise<{ success: boolean; entriesImported?: number; error?: string }> =>
188
+ ipcRenderer.invoke('dictionary:retrieve-from-sharepoint', { fileUrl }),
189
+ sharePointLogin: (): Promise<{ success: boolean; error?: string }> =>
190
+ ipcRenderer.invoke('dictionary:sharepoint-login'),
191
+ isSharePointAuthenticated: (): Promise<{ authenticated: boolean }> =>
192
+ ipcRenderer.invoke('dictionary:is-sharepoint-authenticated'),
193
+ onSyncProgress: (callback: (progress: SyncProgressUpdate) => void) => {
194
+ const subscription = (_event: IpcRendererEvent, progress: SyncProgressUpdate) => callback(progress);
195
+ ipcRenderer.on('dictionary:sync-progress', subscription);
196
+ return () => ipcRenderer.removeListener('dictionary:sync-progress', subscription);
197
+ },
198
+ onSyncComplete: (callback: (result: DictionarySyncResponse) => void) => {
199
+ const subscription = (_event: IpcRendererEvent, result: DictionarySyncResponse) => callback(result);
200
+ ipcRenderer.on('dictionary:sync-complete', subscription);
201
+ return () => ipcRenderer.removeListener('dictionary:sync-complete', subscription);
202
+ },
203
+ },
204
+
205
+ // Display/Monitor operations
206
+ display: {
207
+ getAllDisplays: () => ipcRenderer.invoke('display:get-all-displays'),
208
+ identifyMonitors: () => ipcRenderer.invoke('display:identify-monitors'),
209
+ openComparison: (backupPath: string, processedPath: string, monitorIndex: number) =>
210
+ ipcRenderer.invoke('display:open-comparison', { backupPath, processedPath, monitorIndex }),
211
+ },
212
+
213
+ // Auto-updater
214
+ checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
215
+ downloadUpdate: () => ipcRenderer.invoke('download-update'),
216
+ installUpdate: () => ipcRenderer.invoke('install-update'),
217
+ getCurrentVersion: () => ipcRenderer.invoke('get-app-version'),
218
+
219
+ // SharePoint update source
220
+ setUpdateProvider: (config: { type: 'github' | 'sharepoint'; sharePointUrl?: string }) =>
221
+ ipcRenderer.invoke('update:set-provider', config),
222
+ testSharePointConnection: (url: string) =>
223
+ ipcRenderer.invoke('update:test-sharepoint-connection', url),
224
+ sharePointLogin: () => ipcRenderer.invoke('update:sharepoint-login'),
225
+ sharePointLogout: () => ipcRenderer.invoke('update:sharepoint-logout'),
226
+
227
+ // Update event listeners
228
+ onUpdateChecking: (callback: () => void) => {
229
+ const subscription = (_event: IpcRendererEvent) => callback();
230
+ ipcRenderer.on('update-checking', subscription);
231
+ return () => ipcRenderer.removeListener('update-checking', subscription);
232
+ },
233
+ onUpdateAvailable: (
234
+ callback: (info: { version: string; releaseDate: string; releaseNotes: string }) => void
235
+ ) => {
236
+ const subscription = (_event: IpcRendererEvent, info: any) => callback(info);
237
+ ipcRenderer.on('update-available', subscription);
238
+ return () => ipcRenderer.removeListener('update-available', subscription);
239
+ },
240
+ onUpdateNotAvailable: (callback: (info: { version: string }) => void) => {
241
+ const subscription = (_event: IpcRendererEvent, info: any) => callback(info);
242
+ ipcRenderer.on('update-not-available', subscription);
243
+ return () => ipcRenderer.removeListener('update-not-available', subscription);
244
+ },
245
+ onUpdateError: (callback: (error: { message: string }) => void) => {
246
+ const subscription = (_event: IpcRendererEvent, error: any) => callback(error);
247
+ ipcRenderer.on('update-error', subscription);
248
+ return () => ipcRenderer.removeListener('update-error', subscription);
249
+ },
250
+ onUpdateDownloadProgress: (
251
+ callback: (progress: {
252
+ bytesPerSecond: number;
253
+ percent: number;
254
+ transferred: number;
255
+ total: number;
256
+ }) => void
257
+ ) => {
258
+ const subscription = (_event: IpcRendererEvent, progress: any) => callback(progress);
259
+ ipcRenderer.on('update-download-progress', subscription);
260
+ return () => ipcRenderer.removeListener('update-download-progress', subscription);
261
+ },
262
+ onUpdateDownloaded: (
263
+ callback: (info: { version: string; releaseNotes: string; fallbackUsed?: boolean }) => void
264
+ ) => {
265
+ const subscription = (_event: IpcRendererEvent, info: any) => callback(info);
266
+ ipcRenderer.on('update-downloaded', subscription);
267
+ return () => ipcRenderer.removeListener('update-downloaded', subscription);
268
+ },
269
+ onUpdateFallbackMode: (callback: (data: { message: string }) => void) => {
270
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
271
+ ipcRenderer.on('update-fallback-mode', subscription);
272
+ return () => ipcRenderer.removeListener('update-fallback-mode', subscription);
273
+ },
274
+ onUpdateExtracting: (callback: (data: { message: string }) => void) => {
275
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
276
+ ipcRenderer.on('update-extracting', subscription);
277
+ return () => ipcRenderer.removeListener('update-extracting', subscription);
278
+ },
279
+ onUpdateStatus: (callback: (data: { message: string }) => void) => {
280
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
281
+ ipcRenderer.on('update-status', subscription);
282
+ return () => ipcRenderer.removeListener('update-status', subscription);
283
+ },
284
+
285
+ // Manual download
286
+ openUpdateInBrowser: () => ipcRenderer.invoke('open-update-in-browser'),
287
+
288
+ // Certificate Management
289
+ checkZscalerStatus: () => ipcRenderer.invoke('check-zscaler-status'),
290
+ getCertificatePath: () => ipcRenderer.invoke('get-certificate-path'),
291
+ getInstalledCertificates: () => ipcRenderer.invoke('get-installed-certificates'),
292
+ importCertificate: () => ipcRenderer.invoke('import-certificate'),
293
+ autoDetectCertificates: () => ipcRenderer.invoke('auto-detect-certificates'),
294
+ removeCertificate: (certPath: string) => ipcRenderer.invoke('remove-certificate', certPath),
295
+ testGitHubConnection: () => ipcRenderer.invoke('test-github-connection'),
296
+ openExternal: (url: string) => ipcRenderer.invoke('open-external', url),
297
+
298
+ // Event system helpers (SECURITY: restricted to allowed channels only)
299
+ on: (channel: string, callback: (...args: any[]) => void) => {
300
+ const allowedChannels = [
301
+ 'window-maximized', 'window-unmaximized',
302
+ 'window-fullscreen', 'window-unfullscreen',
303
+ 'window-always-on-top-changed',
304
+ 'hyperlink:batch-progress',
305
+ 'dictionary:sync-progress', 'dictionary:sync-complete',
306
+ 'update-checking', 'update-available', 'update-not-available',
307
+ 'update-error', 'update-download-progress', 'update-downloaded',
308
+ 'update-fallback-mode', 'update-extracting', 'update-status',
309
+ 'update-manual-download',
310
+ 'debug-network-request', 'debug-cert-error',
311
+ 'debug-network-error', 'debug-tls-error',
312
+ 'certificate-check-complete', 'certificate-configured',
313
+ ];
314
+ if (!allowedChannels.includes(channel)) {
315
+ console.error(`[Preload] Blocked IPC listener on disallowed channel: ${channel}`);
316
+ return () => {}; // no-op unsubscribe
317
+ }
318
+ const subscription = (_event: IpcRendererEvent, ...args: any[]) => callback(...args);
319
+ ipcRenderer.on(channel, subscription);
320
+ return () => ipcRenderer.removeListener(channel, subscription);
321
+ },
322
+ removeListener: (
323
+ channel: string,
324
+ callback: (event: IpcRendererEvent, ...args: any[]) => void
325
+ ) => {
326
+ // Same allowlist as on() — only permitted channels can be unsubscribed
327
+ const allowedChannels = [
328
+ 'window-maximized', 'window-unmaximized',
329
+ 'window-fullscreen', 'window-unfullscreen',
330
+ 'window-always-on-top-changed',
331
+ 'hyperlink:batch-progress',
332
+ 'dictionary:sync-progress', 'dictionary:sync-complete',
333
+ 'update-checking', 'update-available', 'update-not-available',
334
+ 'update-error', 'update-download-progress', 'update-downloaded',
335
+ 'update-fallback-mode', 'update-extracting', 'update-status',
336
+ 'update-manual-download',
337
+ 'debug-network-request', 'debug-cert-error',
338
+ 'debug-network-error', 'debug-tls-error',
339
+ 'certificate-check-complete', 'certificate-configured',
340
+ ];
341
+ if (!allowedChannels.includes(channel)) return;
342
+ ipcRenderer.removeListener(channel, callback);
343
+ },
344
+
345
+ // Debug events
346
+ onDebugNetworkRequest: (callback: (data: any) => void) => {
347
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
348
+ ipcRenderer.on('debug-network-request', subscription);
349
+ return () => ipcRenderer.removeListener('debug-network-request', subscription);
350
+ },
351
+ onDebugCertError: (callback: (data: any) => void) => {
352
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
353
+ ipcRenderer.on('debug-cert-error', subscription);
354
+ return () => ipcRenderer.removeListener('debug-cert-error', subscription);
355
+ },
356
+ onDebugNetworkError: (callback: (data: any) => void) => {
357
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
358
+ ipcRenderer.on('debug-network-error', subscription);
359
+ return () => ipcRenderer.removeListener('debug-network-error', subscription);
360
+ },
361
+ onDebugTLSError: (callback: (data: any) => void) => {
362
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
363
+ ipcRenderer.on('debug-tls-error', subscription);
364
+ return () => ipcRenderer.removeListener('debug-tls-error', subscription);
365
+ },
366
+ onUpdateManualDownload: (callback: (data: { message: string; downloadUrl: string }) => void) => {
367
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
368
+ ipcRenderer.on('update-manual-download', subscription);
369
+ return () => ipcRenderer.removeListener('update-manual-download', subscription);
370
+ },
371
+
372
+ // Certificate check events (background)
373
+ onCertificateCheckComplete: (callback: (data: { success: boolean; error?: string; timestamp: string }) => void) => {
374
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
375
+ ipcRenderer.on('certificate-check-complete', subscription);
376
+ return () => ipcRenderer.removeListener('certificate-check-complete', subscription);
377
+ },
378
+
379
+ onCertificateConfigured: (callback: (data: { message: string; certPath?: string }) => void) => {
380
+ const subscription = (_event: IpcRendererEvent, data: any) => callback(data);
381
+ ipcRenderer.on('certificate-configured', subscription);
382
+ return () => ipcRenderer.removeListener('certificate-configured', subscription);
383
+ },
384
+ };
385
+
386
+ // Expose to window using contextBridge (required for contextIsolation: true)
387
+ // This is the correct, secure way to expose APIs to the renderer process
388
+ contextBridge.exposeInMainWorld('electronAPI', electronAPI);
389
+
390
+ // TYPE EXPORT: The ElectronAPI type is now defined in src/types/electron.ts
391
+ // This provides a single source of truth for the API interface, shared between
392
+ // the preload script and the renderer process.
393
+ // Import from '@/types/electron' in renderer code for full type safety.
394
+