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,248 @@
1
+ /**
2
+ * MemoryMonitor - Real-time memory usage tracking and debugging
3
+ *
4
+ * Provides detailed heap usage statistics and warnings for memory-intensive operations
5
+ * like document processing.
6
+ */
7
+
8
+ import logger from './logger';
9
+ import v8 from 'v8';
10
+
11
+ export interface MemoryStats {
12
+ heapUsed: number;
13
+ heapTotal: number;
14
+ heapLimit: number;
15
+ external: number;
16
+ arrayBuffers: number;
17
+ percentUsed: number;
18
+ percentOfLimit: number;
19
+ timestamp: number;
20
+ }
21
+
22
+ export interface MemoryWarning {
23
+ level: 'info' | 'warning' | 'critical';
24
+ message: string;
25
+ stats: MemoryStats;
26
+ }
27
+
28
+ export class MemoryMonitor {
29
+ private static readonly WARNING_THRESHOLD = 0.7; // 70%
30
+ private static readonly CRITICAL_THRESHOLD = 0.85; // 85%
31
+ private static checkpoints: Map<string, MemoryStats> = new Map();
32
+
33
+ /**
34
+ * Get current memory usage statistics
35
+ */
36
+ static getMemoryStats(): MemoryStats {
37
+ const usage = process.memoryUsage();
38
+
39
+ // Get actual V8 heap limit (set via --max-old-space-size or default ~1.4GB)
40
+ const heapStats = v8.getHeapStatistics();
41
+ const heapLimit = heapStats.heap_size_limit;
42
+
43
+ return {
44
+ heapUsed: usage.heapUsed,
45
+ heapTotal: usage.heapTotal,
46
+ heapLimit: heapLimit,
47
+ external: usage.external,
48
+ arrayBuffers: usage.arrayBuffers || 0,
49
+ // Calculate percentage against actual V8 limit, not current allocation
50
+ percentUsed: (usage.heapUsed / heapLimit) * 100,
51
+ percentOfLimit: (usage.heapUsed / heapLimit) * 100,
52
+ timestamp: Date.now(),
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Format memory size in human-readable format
58
+ */
59
+ static formatBytes(bytes: number): string {
60
+ if (bytes === 0) return '0 B';
61
+ const k = 1024;
62
+ const sizes = ['B', 'KB', 'MB', 'GB'];
63
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
64
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
65
+ }
66
+
67
+ /**
68
+ * Log memory usage with a labeled checkpoint
69
+ */
70
+ static logMemoryUsage(label: string, details?: string): MemoryStats {
71
+ const stats = this.getMemoryStats();
72
+ const warning = this.checkThreshold(stats);
73
+
74
+ // Store checkpoint for comparison
75
+ this.checkpoints.set(label, stats);
76
+
77
+ // Build log message - show percentage of actual limit, not current allocation
78
+ const logParts = [
79
+ `[Memory] ${label}:`,
80
+ `Used: ${this.formatBytes(stats.heapUsed)}`,
81
+ `/ ${this.formatBytes(stats.heapLimit)} limit`,
82
+ `(${stats.percentUsed.toFixed(1)}% of heap)`,
83
+ ];
84
+
85
+ if (details) {
86
+ logParts.push(`- ${details}`);
87
+ }
88
+
89
+ // Log with appropriate level
90
+ if (warning) {
91
+ if (warning.level === 'critical') {
92
+ logger.error('🚨', logParts.join(' '), '- CRITICAL MEMORY USAGE!');
93
+ } else if (warning.level === 'warning') {
94
+ logger.warn('⚠️', logParts.join(' '), '- High memory usage');
95
+ } else {
96
+ logger.info('ℹ️', logParts.join(' '));
97
+ }
98
+ } else {
99
+ logger.debug('✓', logParts.join(' '));
100
+ }
101
+
102
+ return stats;
103
+ }
104
+
105
+ /**
106
+ * Check if memory usage exceeds thresholds
107
+ */
108
+ static checkThreshold(stats: MemoryStats): MemoryWarning | null {
109
+ const percentUsed = stats.percentUsed / 100;
110
+
111
+ if (percentUsed >= this.CRITICAL_THRESHOLD) {
112
+ return {
113
+ level: 'critical',
114
+ message: `Memory usage critical (${stats.percentUsed.toFixed(1)}% of ${this.formatBytes(stats.heapLimit)} limit). Consider:
115
+ - Reducing document size
116
+ - Optimizing/compressing images
117
+ - Splitting into multiple documents
118
+ - Increasing Node.js heap size (--max-old-space-size)`,
119
+ stats,
120
+ };
121
+ } else if (percentUsed >= this.WARNING_THRESHOLD) {
122
+ return {
123
+ level: 'warning',
124
+ message: `Memory usage high (${stats.percentUsed.toFixed(1)}% of ${this.formatBytes(stats.heapLimit)} limit)`,
125
+ stats,
126
+ };
127
+ }
128
+
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * Compare memory usage between two checkpoints
134
+ */
135
+ static compareCheckpoints(startLabel: string, endLabel: string): void {
136
+ const start = this.checkpoints.get(startLabel);
137
+ const end = this.checkpoints.get(endLabel);
138
+
139
+ if (!start || !end) {
140
+ logger.warn(
141
+ `[Memory] Cannot compare: checkpoint ${!start ? startLabel : endLabel} not found`
142
+ );
143
+ return;
144
+ }
145
+
146
+ const delta = end.heapUsed - start.heapUsed;
147
+ const deltaFormatted = this.formatBytes(Math.abs(delta));
148
+ const sign = delta >= 0 ? '+' : '-';
149
+ const duration = end.timestamp - start.timestamp;
150
+
151
+ logger.debug(
152
+ `[Memory] Delta (${startLabel} → ${endLabel}): ${sign}${deltaFormatted} in ${duration}ms`
153
+ );
154
+ }
155
+
156
+ /**
157
+ * Force garbage collection if available (requires --expose-gc flag)
158
+ */
159
+ static forceGC(): boolean {
160
+ if (global.gc) {
161
+ logger.debug('[Memory] Forcing garbage collection...');
162
+ const before = this.getMemoryStats();
163
+ global.gc();
164
+ const after = this.getMemoryStats();
165
+ const freed = before.heapUsed - after.heapUsed;
166
+ logger.info(`[Memory] GC freed ${this.formatBytes(freed)}`);
167
+ return true;
168
+ } else {
169
+ logger.warn('[Memory] Garbage collection not available (run with --expose-gc)');
170
+ return false;
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Get memory usage summary for reporting
176
+ */
177
+ static getSummary(): string {
178
+ const stats = this.getMemoryStats();
179
+ return [
180
+ 'Memory Usage Summary:',
181
+ ` Heap Used: ${this.formatBytes(stats.heapUsed)} (${stats.percentUsed.toFixed(1)}%)`,
182
+ ` Heap Total: ${this.formatBytes(stats.heapTotal)}`,
183
+ ` External: ${this.formatBytes(stats.external)}`,
184
+ ` Array Buffers: ${this.formatBytes(stats.arrayBuffers)}`,
185
+ ].join('\n');
186
+ }
187
+
188
+ /**
189
+ * Check if memory is safe to proceed with operation
190
+ */
191
+ static isSafeToProcess(requiredBytes?: number): boolean {
192
+ const stats = this.getMemoryStats();
193
+ const warning = this.checkThreshold(stats);
194
+
195
+ // Critical level - don't proceed
196
+ if (warning && warning.level === 'critical') {
197
+ logger.error(
198
+ '🚨 [Memory] Cannot process document safely. Memory usage critical:',
199
+ warning.message
200
+ );
201
+ return false;
202
+ }
203
+
204
+ // If required bytes specified, check if we have enough headroom
205
+ if (requiredBytes) {
206
+ const available = stats.heapLimit - stats.heapUsed;
207
+ const needsHeadroom = requiredBytes * 1.5; // 50% safety margin
208
+
209
+ if (available < needsHeadroom) {
210
+ logger.warn(
211
+ `[Memory] Insufficient memory. Need ${this.formatBytes(needsHeadroom)}, have ${this.formatBytes(available)}`
212
+ );
213
+ return false;
214
+ }
215
+ }
216
+
217
+ return true;
218
+ }
219
+
220
+ /**
221
+ * Monitor operation with automatic memory logging
222
+ */
223
+ static async monitorOperation<T>(operationName: string, operation: () => Promise<T>): Promise<T> {
224
+ const startLabel = `${operationName}-start`;
225
+ const endLabel = `${operationName}-end`;
226
+
227
+ this.logMemoryUsage(startLabel, 'Starting operation');
228
+
229
+ try {
230
+ const result = await operation();
231
+ this.logMemoryUsage(endLabel, 'Operation completed');
232
+ this.compareCheckpoints(startLabel, endLabel);
233
+ return result;
234
+ } catch (error) {
235
+ this.logMemoryUsage(`${operationName}-error`, 'Operation failed');
236
+ throw error;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Clear all stored checkpoints
242
+ */
243
+ static clearCheckpoints(): void {
244
+ this.checkpoints.clear();
245
+ }
246
+ }
247
+
248
+ export default MemoryMonitor;
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,306 @@
1
+ import logger from './logger';
2
+
3
+ // Convert hex color to HSL format for CSS variables
4
+ export function hexToHSL(hex: string): string {
5
+ try {
6
+ // Validate input
7
+ if (!hex || typeof hex !== 'string') {
8
+ logger.error('[ColorConvert] Invalid hex color input:', hex);
9
+ return '0 0% 50%'; // Default to medium gray
10
+ }
11
+
12
+ // Remove the hash if present
13
+ hex = hex.replace(/^#/, '');
14
+
15
+ // Validate hex format (must be 6 characters of 0-9, A-F)
16
+ if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
17
+ logger.error('[ColorConvert] Invalid hex format:', hex);
18
+ return '0 0% 50%'; // Default to medium gray
19
+ }
20
+
21
+ // Parse the hex values
22
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
23
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
24
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
25
+
26
+ // Validate parsed values
27
+ if (isNaN(r) || isNaN(g) || isNaN(b)) {
28
+ logger.error('[ColorConvert] Failed to parse hex values:', hex);
29
+ return '0 0% 50%';
30
+ }
31
+
32
+ const max = Math.max(r, g, b);
33
+ const min = Math.min(r, g, b);
34
+ let h = 0;
35
+ let s = 0;
36
+ const l = (max + min) / 2;
37
+
38
+ if (max !== min) {
39
+ const d = max - min;
40
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
41
+
42
+ switch (max) {
43
+ case r:
44
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
45
+ break;
46
+ case g:
47
+ h = ((b - r) / d + 2) / 6;
48
+ break;
49
+ case b:
50
+ h = ((r - g) / d + 4) / 6;
51
+ break;
52
+ }
53
+ }
54
+
55
+ // Convert to CSS HSL format (H in degrees, S and L in percentages)
56
+ const hDegrees = Math.round(h * 360);
57
+ const sPercent = Math.round(s * 100);
58
+ const lPercent = Math.round(l * 100);
59
+
60
+ // Validate final values
61
+ if (isNaN(hDegrees) || isNaN(sPercent) || isNaN(lPercent)) {
62
+ logger.error('[ColorConvert] Invalid HSL values calculated from:', hex);
63
+ return '0 0% 50%';
64
+ }
65
+
66
+ // Return in Tailwind CSS variable format
67
+ return `${hDegrees} ${sPercent}% ${lPercent}%`;
68
+ } catch (error) {
69
+ logger.error('[ColorConvert] Unexpected error in hexToHSL:', error, 'Input:', hex);
70
+ return '0 0% 50%'; // Safe fallback
71
+ }
72
+ }
73
+
74
+ // Convert hex to RGB format
75
+ export function hexToRGB(hex: string): string {
76
+ hex = hex.replace(/^#/, '');
77
+
78
+ const r = parseInt(hex.substring(0, 2), 16);
79
+ const g = parseInt(hex.substring(2, 4), 16);
80
+ const b = parseInt(hex.substring(4, 6), 16);
81
+
82
+ return `${r} ${g} ${b}`;
83
+ }
84
+
85
+ /**
86
+ * Calculate relative luminance of a color using the WCAG 2.1 formula
87
+ * @param hex Hex color string (e.g., '#FFFFFF' or 'FFFFFF')
88
+ * @returns Luminance value between 0 (black) and 1 (white)
89
+ */
90
+ export function calculateLuminance(hex: string): number {
91
+ try {
92
+ // Validate input
93
+ if (!hex || typeof hex !== 'string') {
94
+ logger.error('[ColorConvert] Invalid hex input for luminance:', hex);
95
+ return 0.5; // Default to medium luminance
96
+ }
97
+
98
+ // Remove hash if present
99
+ hex = hex.replace(/^#/, '');
100
+
101
+ // Validate hex format
102
+ if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
103
+ logger.error('[ColorConvert] Invalid hex format for luminance:', hex);
104
+ return 0.5;
105
+ }
106
+
107
+ // Parse RGB values (0-255) and normalize to 0-1
108
+ const r = parseInt(hex.substring(0, 2), 16) / 255;
109
+ const g = parseInt(hex.substring(2, 4), 16) / 255;
110
+ const b = parseInt(hex.substring(4, 6), 16) / 255;
111
+
112
+ // Validate parsed values
113
+ if (isNaN(r) || isNaN(g) || isNaN(b)) {
114
+ logger.error('[ColorConvert] Failed to parse RGB for luminance:', hex);
115
+ return 0.5;
116
+ }
117
+
118
+ // Apply gamma correction (sRGB to linear RGB)
119
+ // WCAG 2.1 formula: if value <= 0.03928, divide by 12.92, else apply power function
120
+ const gammaCorrect = (channel: number): number => {
121
+ return channel <= 0.03928 ? channel / 12.92 : Math.pow((channel + 0.055) / 1.055, 2.4);
122
+ };
123
+
124
+ const rLinear = gammaCorrect(r);
125
+ const gLinear = gammaCorrect(g);
126
+ const bLinear = gammaCorrect(b);
127
+
128
+ // Calculate relative luminance using WCAG coefficients
129
+ // These weights account for human eye sensitivity to different colors
130
+ const luminance = 0.2126 * rLinear + 0.7152 * gLinear + 0.0722 * bLinear;
131
+
132
+ // Validate result
133
+ if (isNaN(luminance)) {
134
+ logger.error('[ColorConvert] Invalid luminance calculated from:', hex);
135
+ return 0.5;
136
+ }
137
+
138
+ return luminance;
139
+ } catch (error) {
140
+ logger.error('[ColorConvert] Unexpected error in calculateLuminance:', error, 'Input:', hex);
141
+ return 0.5; // Safe fallback
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Determine optimal text color (white or black) for a given background color
147
+ * Uses WCAG 2.1 contrast guidelines to ensure readability
148
+ * @param backgroundColor Hex color string (e.g., '#FFFFFF' or 'FFFFFF')
149
+ * @returns '#FFFFFF' for white text or '#000000' for black text
150
+ */
151
+ export function getContrastTextColor(backgroundColor: string): string {
152
+ try {
153
+ // Validate input
154
+ if (!backgroundColor || typeof backgroundColor !== 'string') {
155
+ logger.error('[ColorConvert] Invalid background color for contrast:', backgroundColor);
156
+ return '#000000'; // Default to black text
157
+ }
158
+
159
+ const luminance = calculateLuminance(backgroundColor);
160
+
161
+ // WCAG threshold: luminance > 0.5 suggests light background (use black text)
162
+ // luminance <= 0.5 suggests dark background (use white text)
163
+ return luminance > 0.5 ? '#000000' : '#FFFFFF';
164
+ } catch (error) {
165
+ logger.error(
166
+ '[ColorConvert] Unexpected error in getContrastTextColor:',
167
+ error,
168
+ 'Input:',
169
+ backgroundColor
170
+ );
171
+ return '#000000'; // Safe fallback
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Create a subtle color variation for secondary text
177
+ * Makes white text slightly darker, and black text slightly lighter
178
+ * @param primaryTextColor Primary text color ('#FFFFFF' or '#000000')
179
+ * @returns Slightly adjusted hex color for secondary text
180
+ */
181
+ export function getSecondaryTextColor(primaryTextColor: string): string {
182
+ try {
183
+ // Validate input
184
+ if (!primaryTextColor || typeof primaryTextColor !== 'string') {
185
+ logger.error('[ColorConvert] Invalid primary text color for secondary:', primaryTextColor);
186
+ return '#4D4D4D'; // Default to gray
187
+ }
188
+
189
+ const isWhite =
190
+ primaryTextColor.toUpperCase() === '#FFFFFF' || primaryTextColor.toUpperCase() === 'FFFFFF';
191
+
192
+ if (isWhite) {
193
+ // White text -> slightly darker (85% opacity equivalent = #D9D9D9)
194
+ return '#D9D9D9';
195
+ } else {
196
+ // Black text -> slightly lighter (70% opacity equivalent = #4D4D4D)
197
+ return '#4D4D4D';
198
+ }
199
+ } catch (error) {
200
+ logger.error(
201
+ '[ColorConvert] Unexpected error in getSecondaryTextColor:',
202
+ error,
203
+ 'Input:',
204
+ primaryTextColor
205
+ );
206
+ return '#4D4D4D'; // Safe fallback
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Parse a hex color string into R, G, B components (0-255)
212
+ * @param hex Hex color string (e.g., '#FFFFFF' or 'FFFFFF')
213
+ * @returns Object with r, g, b values (0-255)
214
+ */
215
+ function parseHex(hex: string): { r: number; g: number; b: number } {
216
+ hex = hex.replace(/^#/, '');
217
+ return {
218
+ r: parseInt(hex.substring(0, 2), 16),
219
+ g: parseInt(hex.substring(2, 4), 16),
220
+ b: parseInt(hex.substring(4, 6), 16),
221
+ };
222
+ }
223
+
224
+ /**
225
+ * Convert R, G, B components (0-255) to a hex color string
226
+ */
227
+ function toHex(r: number, g: number, b: number): string {
228
+ const clamp = (v: number) => Math.max(0, Math.min(255, Math.round(v)));
229
+ return `#${clamp(r).toString(16).padStart(2, '0')}${clamp(g).toString(16).padStart(2, '0')}${clamp(b).toString(16).padStart(2, '0')}`;
230
+ }
231
+
232
+ /**
233
+ * Adjust a color's lightness by mixing it toward black (darken) or white (lighten).
234
+ * @param hex The color to adjust
235
+ * @param factor Amount to adjust (0 = no change, 1 = fully black/white)
236
+ * @param direction 'darken' or 'lighten'
237
+ * @returns Adjusted hex color
238
+ */
239
+ function adjustLightness(hex: string, factor: number, direction: 'darken' | 'lighten'): string {
240
+ const { r, g, b } = parseHex(hex);
241
+ const target = direction === 'darken' ? 0 : 255;
242
+ return toHex(
243
+ r + (target - r) * factor,
244
+ g + (target - g) * factor,
245
+ b + (target - b) * factor
246
+ );
247
+ }
248
+
249
+ /**
250
+ * Calculate the WCAG contrast ratio between two colors.
251
+ * @returns Contrast ratio (1:1 to 21:1)
252
+ */
253
+ export function getContrastRatio(hex1: string, hex2: string): number {
254
+ const lum1 = calculateLuminance(hex1);
255
+ const lum2 = calculateLuminance(hex2);
256
+ const lighter = Math.max(lum1, lum2);
257
+ const darker = Math.min(lum1, lum2);
258
+ return (lighter + 0.05) / (darker + 0.05);
259
+ }
260
+
261
+ /**
262
+ * Ensure a primary accent color is readable when used as text against the background.
263
+ * If the contrast ratio is too low (e.g., white primary on white background),
264
+ * the color is progressively darkened or lightened until it meets a 3:1 contrast ratio.
265
+ *
266
+ * This does NOT affect bg-primary (backgrounds keep the user's chosen color).
267
+ * It only affects text-primary (standalone accent text/icons on the page background).
268
+ *
269
+ * @param primaryHex The user's chosen primary color
270
+ * @param backgroundHex The user's chosen background color
271
+ * @returns A hex color that maintains the primary hue but is readable against the background
272
+ */
273
+ export function ensureReadablePrimary(primaryHex: string, backgroundHex: string): string {
274
+ try {
275
+ if (!primaryHex || !backgroundHex) {
276
+ logger.error('[ColorConvert] Invalid inputs for ensureReadablePrimary:', { primaryHex, backgroundHex });
277
+ return primaryHex || '#3B82F6'; // Fallback to default blue
278
+ }
279
+
280
+ const contrastRatio = getContrastRatio(primaryHex, backgroundHex);
281
+
282
+ // WCAG AA large text / UI components require 3:1 minimum
283
+ if (contrastRatio >= 3) {
284
+ return primaryHex; // Already readable — no adjustment needed
285
+ }
286
+
287
+ // Determine direction: darken primary on light backgrounds, lighten on dark
288
+ const bgLum = calculateLuminance(backgroundHex);
289
+ const direction: 'darken' | 'lighten' = bgLum > 0.5 ? 'darken' : 'lighten';
290
+
291
+ // Progressively adjust until we hit 3:1 contrast (step in 5% increments)
292
+ for (let step = 0.05; step <= 1.0; step += 0.05) {
293
+ const adjusted = adjustLightness(primaryHex, step, direction);
294
+ const newRatio = getContrastRatio(adjusted, backgroundHex);
295
+ if (newRatio >= 3) {
296
+ return adjusted;
297
+ }
298
+ }
299
+
300
+ // If we couldn't reach 3:1 even at maximum adjustment, use black or white
301
+ return bgLum > 0.5 ? '#000000' : '#FFFFFF';
302
+ } catch (error) {
303
+ logger.error('[ColorConvert] Error in ensureReadablePrimary:', error);
304
+ return primaryHex || '#3B82F6';
305
+ }
306
+ }