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,200 @@
|
|
|
1
|
+
## Problem Description
|
|
2
|
+
|
|
3
|
+
**Type:** Performance (Synchronous Blocking)
|
|
4
|
+
**Priority:** Critical
|
|
5
|
+
**Likelihood:** 90%
|
|
6
|
+
**Impact:** 3-5 second white screen on every app launch
|
|
7
|
+
**Timeline:** **ALREADY HAPPENING** - worse as database grows
|
|
8
|
+
|
|
9
|
+
Four nested context providers execute synchronous initialization in the render path, blocking React from rendering anything until ALL providers complete initialization.
|
|
10
|
+
|
|
11
|
+
### Affected Files
|
|
12
|
+
|
|
13
|
+
- [`src/App.tsx:114-124`](src/App.tsx#L114-L124) - Provider nesting structure
|
|
14
|
+
- [`src/contexts/ThemeContext.tsx:53-146`](src/contexts/ThemeContext.tsx#L53-L146) - 17× localStorage reads
|
|
15
|
+
- [`src/contexts/UserSettingsContext.tsx:123-125`](src/contexts/UserSettingsContext.tsx#L123-L125) - Settings load
|
|
16
|
+
- [`src/contexts/GlobalStatsContext.tsx:38-100`](src/contexts/GlobalStatsContext.tsx#L38-L100) - IndexedDB initialization
|
|
17
|
+
- [`src/contexts/SessionContext.tsx:44-125`](src/contexts/SessionContext.tsx#L44-L125) - Session loading + migration
|
|
18
|
+
|
|
19
|
+
### Current Implementation
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
<ErrorBoundary>
|
|
23
|
+
<ThemeProvider> {/* 17× localStorage reads! */}
|
|
24
|
+
<UserSettingsProvider> {/* localStorage + JSON parse */}
|
|
25
|
+
<GlobalStatsProvider> {/* IndexedDB open + read */}
|
|
26
|
+
<SessionProvider> {/* IndexedDB open + migration + cleanup */}
|
|
27
|
+
<RouterProvider router={router} />
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Initialization Sequence Breakdown
|
|
31
|
+
|
|
32
|
+
**1. ThemeProvider:** ~85-170ms
|
|
33
|
+
|
|
34
|
+
- 17× `localStorage.getItem()` calls in `useState` initializers
|
|
35
|
+
- Each read takes ~5-10ms
|
|
36
|
+
|
|
37
|
+
**2. UserSettingsProvider:** ~10-20ms
|
|
38
|
+
|
|
39
|
+
- Reads 'userSettings' from localStorage
|
|
40
|
+
- JSON parsing
|
|
41
|
+
|
|
42
|
+
**3. GlobalStatsProvider:** ~100-300ms
|
|
43
|
+
|
|
44
|
+
- Opens IndexedDB connection
|
|
45
|
+
- Reads existing stats
|
|
46
|
+
- Performs date rollover calculations
|
|
47
|
+
- **Very slow operation!**
|
|
48
|
+
|
|
49
|
+
**4. SessionProvider:** ~200-500ms (or **2-5 seconds with migration!**)
|
|
50
|
+
|
|
51
|
+
- Checks localStorage for old sessions
|
|
52
|
+
- **Migrates from localStorage to IndexedDB** if needed
|
|
53
|
+
- Loads ALL sessions from IndexedDB
|
|
54
|
+
- Deserializes dates for each session
|
|
55
|
+
- Cleans up sessions >30 days old
|
|
56
|
+
- Deletes old sessions from database
|
|
57
|
+
|
|
58
|
+
**Total Blocking Time:**
|
|
59
|
+
|
|
60
|
+
- **Best Case:** 400-1000ms of white/black screen
|
|
61
|
+
- **Migration Scenario:** **3-5 seconds** of frozen UI
|
|
62
|
+
- **With 50+ Sessions:** 1.5-3 seconds
|
|
63
|
+
|
|
64
|
+
## Root Cause
|
|
65
|
+
|
|
66
|
+
All context providers use **synchronous initialization** in `useState` initializers or immediate `useEffect` calls. React **cannot render anything** until all providers complete their setup, blocking the entire UI thread.
|
|
67
|
+
|
|
68
|
+
## Impact on Users
|
|
69
|
+
|
|
70
|
+
### First Launch (Clean Install)
|
|
71
|
+
|
|
72
|
+
1. User clicks app icon
|
|
73
|
+
2. Electron window opens (black screen, `backgroundColor: '#0a0a0a'`)
|
|
74
|
+
3. **400-1000ms pass** while contexts initialize
|
|
75
|
+
4. Finally, React UI appears
|
|
76
|
+
|
|
77
|
+
### Migration Scenario (Upgrading from localStorage)
|
|
78
|
+
|
|
79
|
+
1. User clicks app icon
|
|
80
|
+
2. Black screen appears
|
|
81
|
+
3. **3-5 SECONDS pass** while migration runs
|
|
82
|
+
4. No loading indicator, no progress bar
|
|
83
|
+
5. User thinks app is frozen
|
|
84
|
+
|
|
85
|
+
### Normal Launch (With 50+ Sessions)
|
|
86
|
+
|
|
87
|
+
1. User clicks app icon
|
|
88
|
+
2. Black screen
|
|
89
|
+
3. **1.5-3 seconds** while all sessions load and deserialize
|
|
90
|
+
4. UI finally appears
|
|
91
|
+
|
|
92
|
+
## Evidence of Existing Issues
|
|
93
|
+
|
|
94
|
+
From code comments:
|
|
95
|
+
|
|
96
|
+
- Line 198 in SessionContext: `"PERFORMANCE FIX: Increased debounce from 1s to 3s for better UI responsiveness"` - persistence was too slow!
|
|
97
|
+
- Line 199: "This reduces database writes during active editing (drag-drop, processing, etc.) and makes the UI feel much snappier" - clear performance problem
|
|
98
|
+
|
|
99
|
+
### Scaling Analysis
|
|
100
|
+
|
|
101
|
+
- 10 sessions: ~500ms load time (acceptable)
|
|
102
|
+
- 50 sessions: ~1.5s load time (sluggish)
|
|
103
|
+
- 100 sessions: ~3s+ load time (unusable)
|
|
104
|
+
- 200 sessions: ~5s+ load time (critical)
|
|
105
|
+
|
|
106
|
+
## Proposed Solution
|
|
107
|
+
|
|
108
|
+
Implement **lazy context initialization** with loading states:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// NEW: Deferred provider pattern
|
|
112
|
+
function App() {
|
|
113
|
+
return (
|
|
114
|
+
<ErrorBoundary>
|
|
115
|
+
<ThemeProvider> {/* Only theme - needed for initial colors */}
|
|
116
|
+
<AppShell /> {/* Shows loading UI immediately */}
|
|
117
|
+
</ThemeProvider>
|
|
118
|
+
</ErrorBoundary>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function AppShell() {
|
|
123
|
+
const [isReady, setIsReady] = useState(false);
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
// Initialize heavy contexts in background
|
|
127
|
+
Promise.all([
|
|
128
|
+
initUserSettings(),
|
|
129
|
+
initGlobalStats(),
|
|
130
|
+
initSessions(),
|
|
131
|
+
]).then(() => {
|
|
132
|
+
setIsReady(true);
|
|
133
|
+
});
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
if (!isReady) {
|
|
137
|
+
return <SplashScreen />; // Beautiful loading UI
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<UserSettingsProvider>
|
|
142
|
+
<GlobalStatsProvider>
|
|
143
|
+
<SessionProvider>
|
|
144
|
+
<RouterProvider router={router} />
|
|
145
|
+
</SessionProvider>
|
|
146
|
+
</GlobalStatsProvider>
|
|
147
|
+
</UserSettingsProvider>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Alternative: Code Splitting
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Lazy load heavy providers
|
|
156
|
+
const SessionProvider = lazy(() => import('@/contexts/SessionContext'));
|
|
157
|
+
const GlobalStatsProvider = lazy(() => import('@/contexts/GlobalStatsContext'));
|
|
158
|
+
|
|
159
|
+
<Suspense fallback={<SplashScreen />}>
|
|
160
|
+
<SessionProvider>
|
|
161
|
+
<GlobalStatsProvider>
|
|
162
|
+
<RouterProvider />
|
|
163
|
+
</GlobalStatsProvider>
|
|
164
|
+
</SessionProvider>
|
|
165
|
+
</Suspense>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Acceptance Criteria
|
|
169
|
+
|
|
170
|
+
- [ ] App shows UI within 200ms of window creation
|
|
171
|
+
- [ ] Loading indicator displayed during context initialization
|
|
172
|
+
- [ ] Migration progress shown to user (if applicable)
|
|
173
|
+
- [ ] Session loading paginated (load 20 at a time, not all at once)
|
|
174
|
+
- [ ] ThemeProvider loads synchronously (needed for colors)
|
|
175
|
+
- [ ] Other providers load asynchronously with Suspense
|
|
176
|
+
- [ ] No white/black screen longer than 200ms
|
|
177
|
+
|
|
178
|
+
## Performance Benchmarks
|
|
179
|
+
|
|
180
|
+
**Target Performance:**
|
|
181
|
+
|
|
182
|
+
- Cold start (no data): < 300ms to interactive
|
|
183
|
+
- Normal start (10 sessions): < 500ms to interactive
|
|
184
|
+
- Heavy load (100 sessions): < 1000ms to interactive
|
|
185
|
+
- Migration: Progress indicator visible within 200ms
|
|
186
|
+
|
|
187
|
+
## Testing Strategy
|
|
188
|
+
|
|
189
|
+
1. **Benchmark Test:** Measure time from window creation to first paint
|
|
190
|
+
2. **Migration Test:** Import large localStorage dataset, verify progress shown
|
|
191
|
+
3. **Scaling Test:** Create 100 dummy sessions, verify load time < 1s
|
|
192
|
+
4. **Regression Test:** Ensure all context data still loads correctly
|
|
193
|
+
|
|
194
|
+
## Estimated Effort
|
|
195
|
+
|
|
196
|
+
**4 hours** (2 hours implementation + 2 hours testing + performance tuning)
|
|
197
|
+
|
|
198
|
+
## Research Reference
|
|
199
|
+
|
|
200
|
+
Full analysis: [`GH_Issues/scratchpads/predictive-analysis-2025-10-18.md`](../GH_Issues/scratchpads/predictive-analysis-2025-10-18.md#critical-issue-2-context-provider-cascade-blocks-initial-render)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
## Problem Description
|
|
2
|
+
|
|
3
|
+
**Type:** Bug (Memory Leak)
|
|
4
|
+
**Priority:** Critical
|
|
5
|
+
**Likelihood:** 80%
|
|
6
|
+
**Impact:** App crashes after 30-60 minutes of use
|
|
7
|
+
**Timeline:** 2-4 weeks of normal use → noticeable slowdown; 1-2 months → crashes
|
|
8
|
+
|
|
9
|
+
GlobalStatsProvider creates its own IndexedDB connection instead of using the existing ConnectionPool, potentially leaking connections over time.
|
|
10
|
+
|
|
11
|
+
### Affected Files
|
|
12
|
+
|
|
13
|
+
- [`src/contexts/GlobalStatsContext.tsx:38-100`](src/contexts/GlobalStatsContext.tsx#L38-L100) - Direct openDB() usage
|
|
14
|
+
- [`src/utils/indexedDB.ts:36-186`](src/utils/indexedDB.ts#L36-L186) - Existing ConnectionPool class (not used)
|
|
15
|
+
|
|
16
|
+
### Current Implementation
|
|
17
|
+
|
|
18
|
+
**GlobalStatsProvider (Problematic):**
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
export function GlobalStatsProvider({ children }: { children: ReactNode }) {
|
|
22
|
+
const [db, setDb] = useState<IDBPDatabase<GlobalStatsDB> | null>(null);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
let database: IDBPDatabase<GlobalStatsDB> | null = null;
|
|
26
|
+
let isMounted = true;
|
|
27
|
+
|
|
28
|
+
const initDB = async () => {
|
|
29
|
+
database = await openDB<GlobalStatsDB>(DB_NAME, DB_VERSION, {
|
|
30
|
+
upgrade(db: IDBPDatabase<GlobalStatsDB>) {
|
|
31
|
+
if (!db.objectStoreNames.contains(STATS_STORE)) {
|
|
32
|
+
db.createObjectStore(STATS_STORE);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!isMounted) {
|
|
38
|
+
database.close();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setDb(database); // ❌ Stores DB in state
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
initDB();
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
isMounted = false;
|
|
49
|
+
if (database) {
|
|
50
|
+
database.close(); // Cleanup on unmount
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}, []); // ❌ Empty deps array
|
|
54
|
+
|
|
55
|
+
const updateStats = useCallback(
|
|
56
|
+
async (update: StatsUpdate) => {
|
|
57
|
+
if (!db) return; // Uses db from state
|
|
58
|
+
|
|
59
|
+
setStats((prevStats) => {
|
|
60
|
+
// ... update logic
|
|
61
|
+
db.put(STATS_STORE, updatedStats, STATS_KEY);
|
|
62
|
+
return updatedStats;
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
[db] // ❌ Dependency on db state
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**SessionContext (Correct Pattern):**
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// indexedDB.ts has a singleton connection pool
|
|
74
|
+
class IndexedDBConnectionPool {
|
|
75
|
+
private db: IDBDatabase | null = null;
|
|
76
|
+
|
|
77
|
+
async getConnection(): Promise<IDBDatabase> {
|
|
78
|
+
if (this.db && this.db.objectStoreNames.length > 0) {
|
|
79
|
+
return this.db; // Reuse existing connection
|
|
80
|
+
}
|
|
81
|
+
// ... create new connection only if needed
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const connectionPool = new IndexedDBConnectionPool();
|
|
86
|
+
|
|
87
|
+
export async function saveSession(session: SerializedSession): Promise<void> {
|
|
88
|
+
const db = await connectionPool.getConnection(); // Uses pool
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Root Cause Analysis
|
|
93
|
+
|
|
94
|
+
1. **Separate DB Instance:** GlobalStatsProvider creates its own `openDB()` call instead of using `connectionPool.getConnection()`
|
|
95
|
+
2. **State Dependency:** `db` is stored in state, triggering re-renders when it changes
|
|
96
|
+
3. **Callback Re-creation:** `updateStats` callback depends on `[db]`, so it recreates when db changes
|
|
97
|
+
4. **Potential Leak:** If `setDb()` is called with a new connection before the old one closes (e.g., during reconnection), the old connection is abandoned but not closed
|
|
98
|
+
|
|
99
|
+
## Memory Leak Scenario
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
Time: 0s - App starts, openDB() creates connection A
|
|
103
|
+
Time: 5s - Connection A stored in state via setDb(A)
|
|
104
|
+
Time: 30m - Network error occurs, connection A becomes invalid
|
|
105
|
+
Time: 30m - useEffect cleanup hasn't run (component still mounted)
|
|
106
|
+
Time: 30m - Auto-reconnect logic (if added) calls openDB() again
|
|
107
|
+
Time: 30m - Connection B created, setDb(B) called
|
|
108
|
+
Time: 30m - Connection A is now orphaned! (not closed, not in state)
|
|
109
|
+
Result: Connection A leaks until app restart
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Impact Analysis
|
|
113
|
+
|
|
114
|
+
**Short-term (0-2 weeks):**
|
|
115
|
+
|
|
116
|
+
- No visible issues
|
|
117
|
+
- Single DB connection per session
|
|
118
|
+
|
|
119
|
+
**Medium-term (2-4 weeks):**
|
|
120
|
+
|
|
121
|
+
- Network instability creates reconnections
|
|
122
|
+
- Each orphaned connection holds memory and file handles
|
|
123
|
+
- Gradual slowdown as more connections leak
|
|
124
|
+
|
|
125
|
+
**Long-term (1-2 months):**
|
|
126
|
+
|
|
127
|
+
- Dozens of leaked connections
|
|
128
|
+
- Browser/Electron quota errors
|
|
129
|
+
- App crashes with "Too many open files" or "QuotaExceededError"
|
|
130
|
+
|
|
131
|
+
## Evidence
|
|
132
|
+
|
|
133
|
+
**From indexedDB.ts:**
|
|
134
|
+
|
|
135
|
+
- Lines 36-186: Sophisticated `IndexedDBConnectionPool` class exists
|
|
136
|
+
- Line 189: Singleton instance: `const connectionPool = new IndexedDBConnectionPool()`
|
|
137
|
+
- Lines 211-244: All SessionContext functions use the pool
|
|
138
|
+
- **But GlobalStatsProvider doesn't use it!**
|
|
139
|
+
|
|
140
|
+
## Proposed Solution
|
|
141
|
+
|
|
142
|
+
Refactor GlobalStatsProvider to use connection pool pattern:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// NEW: Create connection pool for GlobalStats
|
|
146
|
+
class GlobalStatsConnectionPool {
|
|
147
|
+
private static instance: GlobalStatsConnectionPool;
|
|
148
|
+
private db: IDBPDatabase<GlobalStatsDB> | null = null;
|
|
149
|
+
|
|
150
|
+
static getInstance(): GlobalStatsConnectionPool {
|
|
151
|
+
if (!GlobalStatsConnectionPool.instance) {
|
|
152
|
+
GlobalStatsConnectionPool.instance = new GlobalStatsConnectionPool();
|
|
153
|
+
}
|
|
154
|
+
return GlobalStatsConnectionPool.instance;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async getConnection(): Promise<IDBPDatabase<GlobalStatsDB>> {
|
|
158
|
+
if (this.db) {
|
|
159
|
+
return this.db; // Reuse connection
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.db = await openDB<GlobalStatsDB>(DB_NAME, DB_VERSION, {
|
|
163
|
+
upgrade(db) {
|
|
164
|
+
if (!db.objectStoreNames.contains(STATS_STORE)) {
|
|
165
|
+
db.createObjectStore(STATS_STORE);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return this.db;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
close(): void {
|
|
174
|
+
if (this.db) {
|
|
175
|
+
this.db.close();
|
|
176
|
+
this.db = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const statsPool = GlobalStatsConnectionPool.getInstance();
|
|
182
|
+
|
|
183
|
+
// REFACTORED Provider
|
|
184
|
+
export function GlobalStatsProvider({ children }: { children: ReactNode }) {
|
|
185
|
+
const [stats, setStats] = useState<GlobalStats>(createDefaultGlobalStats());
|
|
186
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
let isMounted = true;
|
|
190
|
+
|
|
191
|
+
const loadStats = async () => {
|
|
192
|
+
try {
|
|
193
|
+
const db = await statsPool.getConnection(); // Use pool
|
|
194
|
+
|
|
195
|
+
if (!isMounted) return;
|
|
196
|
+
|
|
197
|
+
const existingStats = await db.get(STATS_STORE, STATS_KEY);
|
|
198
|
+
// ... rest of initialization
|
|
199
|
+
} finally {
|
|
200
|
+
if (isMounted) {
|
|
201
|
+
setIsLoading(false);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
loadStats();
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
isMounted = false;
|
|
210
|
+
// NO database.close() - pool manages lifecycle
|
|
211
|
+
};
|
|
212
|
+
}, []);
|
|
213
|
+
|
|
214
|
+
const updateStats = useCallback(
|
|
215
|
+
async (update: StatsUpdate) => {
|
|
216
|
+
const db = await statsPool.getConnection(); // Get from pool
|
|
217
|
+
|
|
218
|
+
setStats((prevStats) => {
|
|
219
|
+
const updatedStats = { ...prevStats };
|
|
220
|
+
// ... update logic
|
|
221
|
+
db.put(STATS_STORE, updatedStats, STATS_KEY);
|
|
222
|
+
return updatedStats;
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
[] // No dependencies - uses pool directly
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Cleanup on app shutdown
|
|
230
|
+
if (typeof window !== 'undefined') {
|
|
231
|
+
window.addEventListener('beforeunload', () => {
|
|
232
|
+
statsPool.close();
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Acceptance Criteria
|
|
238
|
+
|
|
239
|
+
- [ ] GlobalStatsProvider uses connection pool pattern
|
|
240
|
+
- [ ] Only ONE IndexedDB connection created for GlobalStats
|
|
241
|
+
- [ ] Connection persists for entire app lifecycle
|
|
242
|
+
- [ ] No database connections leak on reconnection
|
|
243
|
+
- [ ] updateStats callback doesn't depend on db state
|
|
244
|
+
- [ ] Memory usage stable over 1+ hour session
|
|
245
|
+
- [ ] No "Too many open files" errors
|
|
246
|
+
|
|
247
|
+
## Testing Strategy
|
|
248
|
+
|
|
249
|
+
1. **Memory Leak Test:**
|
|
250
|
+
- Run app for 1 hour with periodic stats updates
|
|
251
|
+
- Monitor open file handles
|
|
252
|
+
- Verify only 1 IndexedDB connection for GlobalStats exists
|
|
253
|
+
|
|
254
|
+
2. **Reconnection Test:**
|
|
255
|
+
- Simulate network interruption to trigger reconnection
|
|
256
|
+
- Verify no duplicate connections created
|
|
257
|
+
- Check memory doesn't grow after reconnects
|
|
258
|
+
|
|
259
|
+
3. **Long-Running Test:**
|
|
260
|
+
- Process 100 documents over 2 hours
|
|
261
|
+
- Monitor memory usage (should be flat)
|
|
262
|
+
- Verify no crashes or quota errors
|
|
263
|
+
|
|
264
|
+
## Estimated Effort
|
|
265
|
+
|
|
266
|
+
**3 hours** (1.5 hours refactoring + 1.5 hours testing and validation)
|
|
267
|
+
|
|
268
|
+
## Research Reference
|
|
269
|
+
|
|
270
|
+
Full analysis: [`GH_Issues/scratchpads/predictive-analysis-2025-10-18.md`](../GH_Issues/scratchpads/predictive-analysis-2025-10-18.md#critical-issue-3-globalstatsprovider-indexeddb-memory-leak)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
## Problem Description
|
|
2
|
+
|
|
3
|
+
**Type:** Performance (Algorithmic Complexity)
|
|
4
|
+
**Priority:** High
|
|
5
|
+
**Likelihood:** 100%
|
|
6
|
+
**Impact:** App becomes unusable with 50+ sessions
|
|
7
|
+
**Timeline:** 4-6 weeks of normal use → slowdown begins; 2-3 months → critical
|
|
8
|
+
|
|
9
|
+
Every 3 seconds, SessionProvider saves ALL sessions to IndexedDB, which triggers expensive cleanup operations that read all sessions again, creating O(n) complexity.
|
|
10
|
+
|
|
11
|
+
### Affected Files
|
|
12
|
+
|
|
13
|
+
- [`src/contexts/SessionContext.tsx:139-214`](src/contexts/SessionContext.tsx#L139-L214) - Persistence logic
|
|
14
|
+
- [`src/utils/indexedDB.ts:484-528`](src/utils/indexedDB.ts#L484-L528) - Size checking
|
|
15
|
+
- [`src/utils/indexedDB.ts:596-647`](src/utils/indexedDB.ts#L596-L647) - Quota error handling
|
|
16
|
+
|
|
17
|
+
### Current Implementation
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
const debouncedPersistSessions = useCallback(async () => {
|
|
21
|
+
try {
|
|
22
|
+
await ensureDBSizeLimit(200); // ❌ Reads ALL sessions!
|
|
23
|
+
|
|
24
|
+
const serializedSessions = currentSessions.map(/* ... */);
|
|
25
|
+
|
|
26
|
+
// ❌ Save EVERY session on EVERY persist!
|
|
27
|
+
for (const session of serializedSessions) {
|
|
28
|
+
const truncatedSession = truncateSessionChanges(session, 100);
|
|
29
|
+
await handleQuotaExceededError(async () => saveSession(truncatedSession), session.id);
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
log.error('Failed to persist sessions:', err);
|
|
33
|
+
}
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
// ❌ Triggers on EVERY state change!
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (sessions.length > 0) {
|
|
39
|
+
persistTimerRef.current = setTimeout(() => {
|
|
40
|
+
debouncedPersistSessions();
|
|
41
|
+
}, 3000);
|
|
42
|
+
}
|
|
43
|
+
}, [sessions, activeSessions]); // ❌ Dependency on entire arrays!
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The Cascading Problem
|
|
47
|
+
|
|
48
|
+
**Step 1:** User processes 1 document → triggers state change
|
|
49
|
+
**Step 2:** 3 seconds later, persist runs:
|
|
50
|
+
|
|
51
|
+
- `ensureDBSizeLimit(200)` → reads ALL sessions from DB
|
|
52
|
+
- `JSON.stringify(sessions)` → serializes ALL sessions
|
|
53
|
+
- Loop saves ALL sessions (not just changed one)
|
|
54
|
+
- If quota exceeded → reads ALL sessions AGAIN for cleanup
|
|
55
|
+
|
|
56
|
+
## Complexity Analysis
|
|
57
|
+
|
|
58
|
+
| Sessions | Serialize | Size Check | Save All | Cleanup | Total Time | Impact |
|
|
59
|
+
| -------- | --------- | ---------- | -------- | ------- | ---------- | ---------- |
|
|
60
|
+
| 10 | 5ms | 10ms | 50ms | 10ms | ~75ms | Acceptable |
|
|
61
|
+
| 50 | 25ms | 50ms | 250ms | 50ms | ~375ms | Sluggish |
|
|
62
|
+
| 100 | 50ms | 100ms | 500ms | 100ms | ~750ms | Unusable |
|
|
63
|
+
| 200 | 100ms | 200ms | 1000ms | 200ms | ~1500ms | Crash risk |
|
|
64
|
+
|
|
65
|
+
## Evidence
|
|
66
|
+
|
|
67
|
+
**From Code Comments:**
|
|
68
|
+
|
|
69
|
+
- Line 198: `"PERFORMANCE FIX: Increased debounce from 1s to 3s for better UI responsiveness"`
|
|
70
|
+
- This treats the symptom, not the cause
|
|
71
|
+
- Line 142: `await ensureDBSizeLimit(200);` on EVERY persist
|
|
72
|
+
- Line 162: `truncateSessionChanges(session, 100)` - indicates storage problems
|
|
73
|
+
|
|
74
|
+
## Root Cause
|
|
75
|
+
|
|
76
|
+
1. **Full Save on Every Change:** Saves ALL sessions when only 1 changed
|
|
77
|
+
2. **Eager Size Checking:** Checks DB size on every persist (expensive!)
|
|
78
|
+
3. **Array Dependency:** `useEffect([sessions, activeSessions])` triggers on any array change
|
|
79
|
+
4. **No Dirty Tracking:** No way to know which sessions actually changed
|
|
80
|
+
|
|
81
|
+
## Proposed Solution
|
|
82
|
+
|
|
83
|
+
Implement incremental persistence with dirty tracking:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// NEW: Track which sessions changed
|
|
87
|
+
const dirtySessionsRef = useRef<Set<string>>(new Set());
|
|
88
|
+
|
|
89
|
+
const markSessionDirty = (sessionId: string) => {
|
|
90
|
+
dirtySessionsRef.current.add(sessionId);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// REFACTORED: Only save dirty sessions
|
|
94
|
+
const debouncedPersistSessions = useCallback(async () => {
|
|
95
|
+
try {
|
|
96
|
+
const dirtyIds = Array.from(dirtySessionsRef.current);
|
|
97
|
+
|
|
98
|
+
if (dirtyIds.length === 0) return;
|
|
99
|
+
|
|
100
|
+
log.debug(`Saving ${dirtyIds.length} dirty session(s)`);
|
|
101
|
+
|
|
102
|
+
// Only save sessions that changed
|
|
103
|
+
for (const sessionId of dirtyIds) {
|
|
104
|
+
const session = currentSessions.find(s => s.id === sessionId);
|
|
105
|
+
if (!session) continue;
|
|
106
|
+
|
|
107
|
+
const serialized = /* ... serialize session ... */;
|
|
108
|
+
const truncated = truncateSessionChanges(serialized, 100);
|
|
109
|
+
await saveSession(truncated);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
dirtySessionsRef.current.clear();
|
|
113
|
+
|
|
114
|
+
// Check size only once per 10 minutes (not every persist!)
|
|
115
|
+
const lastSizeCheck = localStorage.getItem('lastDBSizeCheck');
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
|
|
118
|
+
if (!lastSizeCheck || now - parseInt(lastSizeCheck) > 10 * 60 * 1000) {
|
|
119
|
+
await ensureDBSizeLimit(200);
|
|
120
|
+
localStorage.setItem('lastDBSizeCheck', now.toString());
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log.error('Failed to persist sessions:', err);
|
|
124
|
+
}
|
|
125
|
+
}, []);
|
|
126
|
+
|
|
127
|
+
// Track session modifications
|
|
128
|
+
const updateSession = (sessionId: string, updates: Partial<Session>) => {
|
|
129
|
+
setSessions(prev => prev.map(s =>
|
|
130
|
+
s.id === sessionId ? { ...s, ...updates, lastModified: new Date() } : s
|
|
131
|
+
));
|
|
132
|
+
markSessionDirty(sessionId); // Mark as needing save
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Acceptance Criteria
|
|
137
|
+
|
|
138
|
+
- [ ] Only modified sessions are persisted (not all sessions)
|
|
139
|
+
- [ ] DB size check runs maximum once per 10 minutes
|
|
140
|
+
- [ ] Dirty tracking correctly identifies changed sessions
|
|
141
|
+
- [ ] Persist time scales linearly with # of changed sessions
|
|
142
|
+
- [ ] 100 sessions with 1 change: < 50ms persist time
|
|
143
|
+
- [ ] No performance degradation as total session count grows
|
|
144
|
+
|
|
145
|
+
## Performance Benchmarks
|
|
146
|
+
|
|
147
|
+
**Target Improvement:**
|
|
148
|
+
|
|
149
|
+
- 10 sessions, 1 change: ~5ms (was ~75ms)
|
|
150
|
+
- 50 sessions, 1 change: ~5ms (was ~375ms)
|
|
151
|
+
- 100 sessions, 1 change: ~5ms (was ~750ms)
|
|
152
|
+
- 100 sessions, 10 changes: ~50ms
|
|
153
|
+
|
|
154
|
+
**15x faster for typical usage**
|
|
155
|
+
|
|
156
|
+
## Testing Strategy
|
|
157
|
+
|
|
158
|
+
1. **Dirty Tracking Test:** Modify 1 of 50 sessions, verify only 1 written
|
|
159
|
+
2. **Scaling Test:** Create 100 sessions, process 1 document, measure < 50ms
|
|
160
|
+
3. **Size Check Test:** Process 10 documents in 5 minutes, verify size check called once
|
|
161
|
+
4. **Regression Test:** Ensure all session data persists correctly
|
|
162
|
+
|
|
163
|
+
## Estimated Effort
|
|
164
|
+
|
|
165
|
+
**6 hours** (3 hours implementation + 2 hours testing + 1 hour performance tuning)
|
|
166
|
+
|
|
167
|
+
## Research Reference
|
|
168
|
+
|
|
169
|
+
Full analysis: [`GH_Issues/scratchpads/predictive-analysis-2025-10-18.md`](../GH_Issues/scratchpads/predictive-analysis-2025-10-18.md#high-priority-issue-4-on%C2%B2-session-persistence-performance)
|