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.
- package/.eslintrc.json +43 -0
- package/.github/workflows/build.yml +64 -0
- package/.github/workflows/ci.yml +39 -0
- package/.vscode/extensions.json +3 -0
- package/Current.md +97 -0
- package/DocHub_Image.png +0 -0
- package/README.md +666 -0
- package/USER_GUIDE.md +1173 -0
- package/Updater.md +311 -0
- package/build/256x256.png +0 -0
- package/build/512x512.png +0 -0
- package/build/app-update.yml +4 -0
- package/build/create-icon.js +208 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon_1024x1024.png +0 -0
- package/dist/assets/Analytics-BpsG9895.js +1 -0
- package/dist/assets/Card-IAZin8kp.js +1 -0
- package/dist/assets/CurrentSession-B-rFkHvf.js +12 -0
- package/dist/assets/Dashboard-C_5gMb0q.js +1 -0
- package/dist/assets/Documents-CqZ25axS.js +1 -0
- package/dist/assets/Input-l89xwXBi.js +1 -0
- package/dist/assets/Reporting-DqdHJY_a.js +1 -0
- package/dist/assets/Search-XNbu5z_3.js +1 -0
- package/dist/assets/SessionManager-lH9hZfzH.js +1 -0
- package/dist/assets/Sessions-ClZOPYNc.js +1 -0
- package/dist/assets/Settings-DUEHGURa.js +11 -0
- package/dist/assets/index-8xUe8ptc.js +24 -0
- package/dist/assets/index-RYyJqF7O.css +1 -0
- package/dist/assets/path-BkOl0AGO.js +1 -0
- package/dist/assets/promises-ID_B9S-h.js +1 -0
- package/dist/assets/urlHelpers-TvgahX0r.js +1 -0
- package/dist/assets/useToast-yRSO1dkm.js +1 -0
- package/dist/assets/vendor-charts-RkGK5ROP.js +36 -0
- package/dist/assets/vendor-db-l0sNRNKZ.js +1 -0
- package/dist/assets/vendor-react-BVZ_anCF.js +4 -0
- package/dist/assets/vendor-search-Dw8P0qyA.js +1 -0
- package/dist/assets/vendor-ui-BU7NfluV.js +53 -0
- package/dist/electron/PowerAutomateApiService-LfW09ZGr.js +147 -0
- package/dist/electron/main-CXkNtyv-.js +19789 -0
- package/dist/electron/main.js +5 -0
- package/dist/electron/preload.js +1 -0
- package/dist/icon.png +0 -0
- package/dist/index.html +27 -0
- package/docs/CODEBASE_ANALYSIS_REPORT.md +309 -0
- package/docs/DEBUG_LOGGING_GUIDE.md +244 -0
- package/docs/README.md +115 -0
- package/docs/TOC_WIRING_GUIDE.md +344 -0
- package/docs/analysis/Bullet_Symbol_Bug_Analysis.md +136 -0
- package/docs/analysis/DOCXMLATER_ANALYSIS_SUMMARY.txt +169 -0
- package/docs/analysis/Document_Processing_Issues_Analysis.md +704 -0
- package/docs/analysis/FIELD_PRESERVATION_ANALYSIS.md +1200 -0
- package/docs/analysis/INDENTATION_PRESERVE_ANALYSIS.md +181 -0
- package/docs/analysis/INDENTATION_PRESERVE_IMPLEMENTATION.md +207 -0
- package/docs/analysis/List_Implementation.md +206 -0
- package/docs/analysis/List_Implementation_Accuracy_Report.md +366 -0
- package/docs/analysis/PROCESSING_OPTIONS_UI_UPDATES.md +220 -0
- package/docs/analysis/RefactorStyles.md +852 -0
- package/docs/analysis/STYLE_PARAMETER_ENHANCEMENT.md +143 -0
- package/docs/analysis/docxmlater-comparison-todo-2025-11-13.md +636 -0
- package/docs/analysis/docxmlater-implementation-analysis-2025-11-13.md +340 -0
- package/docs/analysis/docxmlater-template_ui-integration-analysis.md +263 -0
- package/docs/analysis/github-issues-to-create.md +237 -0
- package/docs/api/API_README.md +538 -0
- package/docs/api/API_REFERENCE.md +751 -0
- package/docs/api/TYPE_DEFINITIONS.md +869 -0
- package/docs/architecture/FONT_EMBEDDING_GUIDE.md +318 -0
- package/docs/architecture/docxmlater-functions-and-structure.md +726 -0
- package/docs/docxmlater-readme.md +1341 -0
- package/docs/fixes/EXECUTION_LOG_TEST_BASE.md +573 -0
- package/docs/fixes/HYPERLINK_TEXT_SANITIZATION.md +253 -0
- package/docs/fixes/README.md +37 -0
- package/docs/github-issues/issue-1-body.md +125 -0
- package/docs/github-issues/issue-10-body.md +850 -0
- package/docs/github-issues/issue-2-body.md +200 -0
- package/docs/github-issues/issue-3-body.md +270 -0
- package/docs/github-issues/issue-4-body.md +169 -0
- package/docs/github-issues/issue-5-body.md +173 -0
- package/docs/github-issues/issue-6-body.md +158 -0
- package/docs/github-issues/issue-7-body.md +171 -0
- package/docs/github-issues/issue-8-body.md +407 -0
- package/docs/github-issues/issue-9-body.md +515 -0
- package/docs/github-issues/issue-tracker.md +274 -0
- package/docs/github-issues/predictive-analysis-2025-10-18.md +2131 -0
- package/docs/implementation/List_Framework_Refactor_Plan.md +336 -0
- package/docs/implementation/PRIMARY_TEXT_COLOR_FEATURE.md +217 -0
- package/docs/implementation/RELEASE_PLAN_v2.1.0.md +362 -0
- package/docs/implementation/RefactorStyles.md +588 -0
- package/docs/implementation/implement-plan.md +489 -0
- package/docs/implementation/missing-helpers-implementation.md +391 -0
- package/docs/implementation/refactor-plan.md +520 -0
- package/docs/implementation/session-implementation-complete.md +233 -0
- package/docs/implementation/session-management-plan.md +250 -0
- package/docs/setup-checklist.md +77 -0
- package/docs/versions/changelog.md +345 -0
- package/electron/customUpdater.ts +656 -0
- package/electron/main.ts +2441 -0
- package/electron/memoryConfig.ts +187 -0
- package/electron/preload.ts +394 -0
- package/electron/proxyConfig.ts +340 -0
- package/electron/services/BackupService.ts +452 -0
- package/electron/services/DictionaryService.ts +402 -0
- package/electron/services/LocalDictionaryLookupService.ts +147 -0
- package/electron/services/PowerAutomateApiService.ts +231 -0
- package/electron/services/SharePointSyncService.ts +474 -0
- package/electron/windowsCertStore.ts +427 -0
- package/electron/zscalerConfig.ts +381 -0
- package/eslint.config.js +92 -0
- package/jest.config.js +52 -0
- package/package.json +214 -0
- package/postcss.config.mjs +6 -0
- package/public/icon.png +0 -0
- package/publish-release.ps1 +5 -0
- package/renovate.json +30 -0
- package/src/App.tsx +216 -0
- package/src/__mocks__/p-limit.js +12 -0
- package/src/__mocks__/styleMock.js +1 -0
- package/src/components/common/BugReportButton.tsx +44 -0
- package/src/components/common/BugReportDialog.tsx +193 -0
- package/src/components/common/Button.tsx +153 -0
- package/src/components/common/Card.tsx +86 -0
- package/src/components/common/ColorPickerDialog.tsx +177 -0
- package/src/components/common/ConfirmDialog.tsx +96 -0
- package/src/components/common/DebugConsole.tsx +275 -0
- package/src/components/common/EmptyState.tsx +183 -0
- package/src/components/common/ErrorBoundary.tsx +98 -0
- package/src/components/common/ErrorDetailsDialog.tsx +153 -0
- package/src/components/common/ErrorFallback.tsx +218 -0
- package/src/components/common/Input.tsx +109 -0
- package/src/components/common/Skeleton.tsx +184 -0
- package/src/components/common/SplashScreen.tsx +81 -0
- package/src/components/common/Toast.tsx +155 -0
- package/src/components/common/Tooltip.tsx +79 -0
- package/src/components/common/UpdateNotification.tsx +320 -0
- package/src/components/comparison/ComparisonWindow.tsx +374 -0
- package/src/components/comparison/SideBySideDiff.tsx +486 -0
- package/src/components/comparison/index.ts +8 -0
- package/src/components/document/DocumentUploader.tsx +288 -0
- package/src/components/document/HyperlinkPreview.tsx +430 -0
- package/src/components/document/HyperlinkService.md +1484 -0
- package/src/components/document/Hyperlink_Technical_Documentation.md +496 -0
- package/src/components/document/InlineChangesView.tsx +707 -0
- package/src/components/document/ProcessingProgress.tsx +303 -0
- package/src/components/document/ProcessingResults.tsx +256 -0
- package/src/components/document/TrackedChangesDetail.tsx +530 -0
- package/src/components/document/TrackedChangesPanel.tsx +546 -0
- package/src/components/document/VirtualDocumentList.tsx +240 -0
- package/src/components/editor/DocumentEditor.tsx +723 -0
- package/src/components/editor/DocumentEditorModal.tsx +640 -0
- package/src/components/editor/EditorQuickActions.tsx +502 -0
- package/src/components/editor/EditorToolbar.tsx +312 -0
- package/src/components/editor/TableEditor.tsx +926 -0
- package/src/components/editor/index.ts +18 -0
- package/src/components/layout/Header.tsx +190 -0
- package/src/components/layout/Sidebar.tsx +313 -0
- package/src/components/layout/TitleBar.tsx +190 -0
- package/src/components/navigation/CommandPalette.tsx +233 -0
- package/src/components/navigation/KeyboardShortcutsModal.tsx +173 -0
- package/src/components/sessions/ChangeItem.tsx +408 -0
- package/src/components/sessions/ChangeViewer.tsx +1155 -0
- package/src/components/sessions/DocumentComparisonModal.tsx +314 -0
- package/src/components/sessions/ProcessingOptions.tsx +297 -0
- package/src/components/sessions/ReplacementsTab.tsx +438 -0
- package/src/components/sessions/RevisionHandlingOptions.tsx +87 -0
- package/src/components/sessions/SessionManager.tsx +188 -0
- package/src/components/sessions/StylesEditor.tsx +1335 -0
- package/src/components/sessions/TabContainer.tsx +151 -0
- package/src/components/sessions/VirtualSessionList.tsx +157 -0
- package/src/components/sessions/sessionToProcessorManager.tsx +420 -0
- package/src/components/settings/CertificateManager.tsx +410 -0
- package/src/components/settings/SegmentedControl.tsx +88 -0
- package/src/components/settings/SettingRow.tsx +52 -0
- package/src/contexts/GlobalStatsContext.tsx +396 -0
- package/src/contexts/SessionContext.tsx +2129 -0
- package/src/contexts/ThemeContext.tsx +428 -0
- package/src/contexts/UserSettingsContext.tsx +290 -0
- package/src/contexts/__tests__/GlobalStatsContext.test.tsx +390 -0
- package/src/global.d.ts +273 -0
- package/src/hooks/useDocumentQueue.tsx +210 -0
- package/src/hooks/useToast.tsx +55 -0
- package/src/main.tsx +10 -0
- package/src/pages/Analytics.tsx +386 -0
- package/src/pages/CurrentSession.tsx +1174 -0
- package/src/pages/Dashboard.tsx +319 -0
- package/src/pages/Documents.tsx +317 -0
- package/src/pages/Projects.tsx +250 -0
- package/src/pages/Reporting.tsx +386 -0
- package/src/pages/Search.tsx +349 -0
- package/src/pages/Sessions.tsx +285 -0
- package/src/pages/Settings.tsx +2662 -0
- package/src/services/HyperlinkService.ts +1085 -0
- package/src/services/document/DocXMLaterProcessor.ts +617 -0
- package/src/services/document/DocumentProcessingComparison.ts +856 -0
- package/src/services/document/DocumentSnapshotService.ts +575 -0
- package/src/services/document/WordDocumentProcessor.ts +10509 -0
- package/src/services/document/__tests__/DocXMLaterProcessor.hyperlinks.test.md +311 -0
- package/src/services/document/__tests__/WordDocumentProcessor.integration.test.ts +515 -0
- package/src/services/document/__tests__/WordDocumentProcessor.test.ts +812 -0
- package/src/services/document/blanklines/BlankLineManager.ts +658 -0
- package/src/services/document/blanklines/__tests__/paragraphChecks.test.ts +281 -0
- package/src/services/document/blanklines/helpers/blankLineInsertion.ts +87 -0
- package/src/services/document/blanklines/helpers/blankLineSnapshot.ts +251 -0
- package/src/services/document/blanklines/helpers/clearCustom.ts +121 -0
- package/src/services/document/blanklines/helpers/contextChecks.ts +117 -0
- package/src/services/document/blanklines/helpers/imageChecks.ts +51 -0
- package/src/services/document/blanklines/helpers/paragraphChecks.ts +236 -0
- package/src/services/document/blanklines/helpers/removeBlanksBetweenListItems.ts +91 -0
- package/src/services/document/blanklines/helpers/removeTrailingBlanks.ts +35 -0
- package/src/services/document/blanklines/helpers/tableGuards.ts +21 -0
- package/src/services/document/blanklines/index.ts +67 -0
- package/src/services/document/blanklines/rules/additionRules.ts +337 -0
- package/src/services/document/blanklines/rules/indentationRules.ts +317 -0
- package/src/services/document/blanklines/rules/removalRules.ts +362 -0
- package/src/services/document/blanklines/rules/ruleTypes.ts +92 -0
- package/src/services/document/blanklines/types.ts +29 -0
- package/src/services/document/helpers/ImageBorderCropper.ts +377 -0
- package/src/services/document/helpers/__tests__/whitespace.test.ts +272 -0
- package/src/services/document/helpers/whitespace.ts +117 -0
- package/src/services/document/list/ListNormalizer.ts +947 -0
- package/src/services/document/list/index.ts +45 -0
- package/src/services/document/list/list-detection.ts +275 -0
- package/src/services/document/list/list-types.ts +162 -0
- package/src/services/document/processors/HyperlinkProcessor.ts +370 -0
- package/src/services/document/processors/ListProcessor.ts +257 -0
- package/src/services/document/processors/StructureProcessor.ts +176 -0
- package/src/services/document/processors/StyleProcessor.ts +389 -0
- package/src/services/document/processors/TableProcessor.ts +2238 -0
- package/src/services/document/processors/__tests__/HyperlinkProcessor.test.ts +314 -0
- package/src/services/document/processors/__tests__/ListProcessor.test.ts +291 -0
- package/src/services/document/processors/__tests__/StructureProcessor.test.ts +257 -0
- package/src/services/document/processors/__tests__/TableProcessor.hlp-tips-bullets.test.ts +459 -0
- package/src/services/document/processors/__tests__/TableProcessor.test.ts +1604 -0
- package/src/services/document/processors/index.ts +28 -0
- package/src/services/document/types/docx-processing.ts +310 -0
- package/src/services/editor/EditorActionHandlers.ts +901 -0
- package/src/services/editor/index.ts +13 -0
- package/src/setupTests.ts +47 -0
- package/src/styles/global.css +782 -0
- package/src/types/backup.ts +132 -0
- package/src/types/dictionary.ts +125 -0
- package/src/types/document-processing.ts +331 -0
- package/src/types/docxmlater-augments.d.ts +142 -0
- package/src/types/editor.ts +280 -0
- package/src/types/electron.ts +340 -0
- package/src/types/globalStats.ts +155 -0
- package/src/types/hyperlink.ts +471 -0
- package/src/types/operations.ts +354 -0
- package/src/types/session.ts +427 -0
- package/src/types/settings.ts +112 -0
- package/src/utils/MemoryMonitor.ts +248 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/colorConvert.ts +306 -0
- package/src/utils/diffUtils.ts +347 -0
- package/src/utils/documentUtils.ts +202 -0
- package/src/utils/electronGuard.ts +62 -0
- package/src/utils/indexedDB.ts +915 -0
- package/src/utils/logger.ts +717 -0
- package/src/utils/pathSecurity.ts +232 -0
- package/src/utils/pathValidator.ts +236 -0
- package/src/utils/processingTimeEstimator.ts +153 -0
- package/src/utils/safeJsonParse.ts +62 -0
- package/src/utils/textSanitizer.ts +162 -0
- package/src/utils/urlHelpers.ts +304 -0
- package/src/utils/urlPatterns.ts +198 -0
- package/src/utils/urlSanitizer.ts +152 -0
- package/src/vite-env.d.ts +11 -0
- package/tsconfig.electron.json +19 -0
- package/tsconfig.json +36 -0
- package/tsconfig.node.json +12 -0
- package/typedoc.json +45 -0
- 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;
|
package/src/utils/cn.ts
ADDED
|
@@ -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
|
+
}
|