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,428 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
+ import { hexToHSL, getContrastTextColor, getSecondaryTextColor, ensureReadablePrimary } from '@/utils/colorConvert';
3
+ import { logger } from '@/utils/logger';
4
+
5
+ type Theme = 'light' | 'dark' | 'system';
6
+ type AccentColor = 'blue' | 'purple' | 'green' | 'orange' | 'pink' | 'cyan' | 'indigo' | 'custom';
7
+ type Density = 'minimal' | 'compact' | 'comfortable';
8
+
9
+ interface ThemeContextType {
10
+ theme: Theme;
11
+ setTheme: (theme: Theme) => void;
12
+ resolvedTheme: 'light' | 'dark';
13
+ accentColor: AccentColor;
14
+ setAccentColor: (color: AccentColor) => void;
15
+ customAccentColor: string;
16
+ setCustomAccentColor: (color: string) => void;
17
+ customPrimaryColor: string;
18
+ setCustomPrimaryColor: (color: string) => void;
19
+ customBackgroundColor: string;
20
+ setCustomBackgroundColor: (color: string) => void;
21
+ customHeaderColor: string;
22
+ setCustomHeaderColor: (color: string) => void;
23
+ customSidebarColor: string;
24
+ setCustomSidebarColor: (color: string) => void;
25
+ customBorderColor: string;
26
+ setCustomBorderColor: (color: string) => void;
27
+ useCustomColors: boolean;
28
+ setUseCustomColors: (use: boolean) => void;
29
+ density: Density;
30
+ setDensity: (density: Density) => void;
31
+ animations: boolean;
32
+ setAnimations: (enabled: boolean) => void;
33
+ blur: boolean;
34
+ setBlur: (enabled: boolean) => void;
35
+ reduceMotion: boolean;
36
+ setReduceMotion: (enabled: boolean) => void;
37
+ fontSize: number;
38
+ setFontSize: (size: number) => void;
39
+ fontFamily: string;
40
+ setFontFamily: (family: string) => void;
41
+ fontWeight: string;
42
+ setFontWeight: (weight: string) => void;
43
+ fontStyle: string;
44
+ setFontStyle: (style: string) => void;
45
+ letterSpacing: number;
46
+ setLetterSpacing: (spacing: number) => void;
47
+ lineHeight: number;
48
+ setLineHeight: (height: number) => void;
49
+ }
50
+
51
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
52
+
53
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
54
+ const log = logger.namespace('ThemeContext');
55
+ const [theme, setTheme] = useState<Theme>(() => {
56
+ const stored = localStorage.getItem('theme') as Theme;
57
+ return stored || 'system';
58
+ });
59
+
60
+ const [accentColor, setAccentColor] = useState<AccentColor>(() => {
61
+ const stored = localStorage.getItem('accentColor') as AccentColor;
62
+ return stored || 'blue';
63
+ });
64
+
65
+ const [customAccentColor, setCustomAccentColor] = useState<string>(() => {
66
+ const stored = localStorage.getItem('customAccentColor');
67
+ return stored || '#8b5cf6';
68
+ });
69
+
70
+ const [customPrimaryColor, setCustomPrimaryColor] = useState<string>(() => {
71
+ const stored = localStorage.getItem('customPrimaryColor');
72
+ return stored || '#3b82f6';
73
+ });
74
+
75
+ const [customBackgroundColor, setCustomBackgroundColor] = useState<string>(() => {
76
+ const stored = localStorage.getItem('customBackgroundColor');
77
+ return stored || '#ffffff';
78
+ });
79
+
80
+ const [customForegroundColor, setCustomForegroundColor] = useState<string>(() => {
81
+ const stored = localStorage.getItem('customForegroundColor');
82
+ return stored || '#020817';
83
+ });
84
+
85
+ const [customHeaderColor, setCustomHeaderColor] = useState<string>(() => {
86
+ const stored = localStorage.getItem('customHeaderColor');
87
+ return stored || '#f8fafc';
88
+ });
89
+
90
+ const [customSidebarColor, setCustomSidebarColor] = useState<string>(() => {
91
+ const stored = localStorage.getItem('customSidebarColor');
92
+ return stored || '#ffffff';
93
+ });
94
+
95
+ const [customBorderColor, setCustomBorderColor] = useState<string>(() => {
96
+ const stored = localStorage.getItem('customBorderColor');
97
+ return stored || '#e2e8f0';
98
+ });
99
+
100
+ const [useCustomColors, setUseCustomColors] = useState<boolean>(() => {
101
+ const stored = localStorage.getItem('useCustomColors');
102
+ return stored === 'true';
103
+ });
104
+
105
+ const [fontSize, setFontSize] = useState<number>(() => {
106
+ const stored = localStorage.getItem('fontSize');
107
+ return stored ? parseInt(stored) : 15;
108
+ });
109
+
110
+ const [fontFamily, setFontFamily] = useState<string>(() => {
111
+ const stored = localStorage.getItem('fontFamily');
112
+ return stored || 'system-ui';
113
+ });
114
+
115
+ const [fontWeight, setFontWeight] = useState<string>(() => {
116
+ const stored = localStorage.getItem('fontWeight');
117
+ return stored || '400';
118
+ });
119
+
120
+ const [fontStyle, setFontStyle] = useState<string>(() => {
121
+ const stored = localStorage.getItem('fontStyle');
122
+ return stored || 'normal';
123
+ });
124
+
125
+ const [letterSpacing, setLetterSpacing] = useState<number>(() => {
126
+ const stored = localStorage.getItem('letterSpacing');
127
+ return stored ? parseFloat(stored) : 0;
128
+ });
129
+
130
+ const [lineHeight, setLineHeight] = useState<number>(() => {
131
+ const stored = localStorage.getItem('lineHeight');
132
+ return stored ? parseFloat(stored) : 1.5;
133
+ });
134
+
135
+ const [density, setDensity] = useState<Density>(() => {
136
+ const stored = localStorage.getItem('density') as Density;
137
+ return stored || 'comfortable';
138
+ });
139
+
140
+ const [animations, setAnimations] = useState<boolean>(() => {
141
+ const stored = localStorage.getItem('animations');
142
+ return stored !== 'false';
143
+ });
144
+
145
+ const [blur, setBlur] = useState<boolean>(() => {
146
+ const stored = localStorage.getItem('blur');
147
+ return stored !== 'false';
148
+ });
149
+
150
+ const [reduceMotion, setReduceMotion] = useState<boolean>(() => {
151
+ const stored = localStorage.getItem('reduceMotion');
152
+ // Default to system preference if not stored
153
+ if (stored === null) {
154
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
155
+ }
156
+ return stored === 'true';
157
+ });
158
+
159
+ const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>('light');
160
+
161
+ useEffect(() => {
162
+ const root = window.document.documentElement;
163
+
164
+ const applyTheme = () => {
165
+ let effectiveTheme: 'light' | 'dark';
166
+
167
+ if (theme === 'system') {
168
+ effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
169
+ ? 'dark'
170
+ : 'light';
171
+ } else {
172
+ effectiveTheme = theme;
173
+ }
174
+
175
+ root.classList.remove('light', 'dark');
176
+ root.classList.add(effectiveTheme);
177
+ setResolvedTheme(effectiveTheme);
178
+ log.info('Theme applied', { theme, effectiveTheme });
179
+ };
180
+
181
+ applyTheme();
182
+ localStorage.setItem('theme', theme);
183
+
184
+ if (theme === 'system') {
185
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
186
+ const handleChange = () => applyTheme();
187
+ mediaQuery.addEventListener('change', handleChange);
188
+ return () => mediaQuery.removeEventListener('change', handleChange);
189
+ }
190
+ }, [theme, log]);
191
+
192
+ useEffect(() => {
193
+ const root = window.document.documentElement;
194
+
195
+ // Apply accent color
196
+ if (accentColor === 'custom') {
197
+ root.setAttribute('data-accent', 'custom');
198
+ // Convert hex to HSL for CSS variables
199
+ const hslColor = hexToHSL(customAccentColor);
200
+ root.style.setProperty('--custom-accent', hslColor);
201
+ localStorage.setItem('customAccentColor', customAccentColor);
202
+ log.info('Custom accent color applied', { customAccentColor });
203
+ } else if (accentColor !== 'blue') {
204
+ root.setAttribute('data-accent', accentColor);
205
+ // Clear custom accent when switching to preset
206
+ root.style.removeProperty('--custom-accent');
207
+ log.info('Preset accent color applied', { accentColor });
208
+ } else {
209
+ root.removeAttribute('data-accent');
210
+ root.style.removeProperty('--custom-accent');
211
+ log.info('Default accent color applied', { accentColor: 'blue' });
212
+ }
213
+
214
+ localStorage.setItem('accentColor', accentColor);
215
+ }, [accentColor, customAccentColor, log]);
216
+
217
+ // Apply custom colors when enabled
218
+ useEffect(() => {
219
+ const root = window.document.documentElement;
220
+
221
+ if (useCustomColors) {
222
+ try {
223
+ root.setAttribute('data-custom-colors', 'true');
224
+
225
+ log.debug('[ThemeContext] Applying custom colors...');
226
+
227
+ // Calculate optimal text colors based on background colors
228
+ const foregroundColor = getContrastTextColor(customBackgroundColor);
229
+ const headerTextColor = getContrastTextColor(customHeaderColor);
230
+ const sidebarTextColor = getContrastTextColor(customSidebarColor);
231
+ const primaryTextColor = getContrastTextColor(customPrimaryColor); // For checkmarks!
232
+ const secondaryFontColor = getSecondaryTextColor(foregroundColor); // Subtle variation of primary text!
233
+ // Readable primary: ensures text-primary (accent icons/badges) is visible against the background
234
+ // E.g., if primary is white and background is white, this darkens the primary for text usage
235
+ const primaryReadableColor = ensureReadablePrimary(customPrimaryColor, customBackgroundColor);
236
+
237
+ // Convert and apply all custom colors
238
+ root.style.setProperty('--custom-primary', hexToHSL(customPrimaryColor));
239
+ root.style.setProperty('--custom-primary-text', hexToHSL(primaryTextColor)); // For checkmarks!
240
+ root.style.setProperty('--custom-primary-readable', hexToHSL(primaryReadableColor)); // For text-primary accent usage!
241
+ root.style.setProperty('--custom-background', hexToHSL(customBackgroundColor));
242
+ root.style.setProperty('--custom-foreground', hexToHSL(foregroundColor)); // Auto-calculated!
243
+ root.style.setProperty('--custom-header', hexToHSL(customHeaderColor));
244
+ root.style.setProperty('--custom-header-text', hexToHSL(headerTextColor)); // Auto-calculated!
245
+ root.style.setProperty('--custom-sidebar', hexToHSL(customSidebarColor));
246
+ root.style.setProperty('--custom-sidebar-text', hexToHSL(sidebarTextColor)); // Auto-calculated!
247
+ root.style.setProperty('--custom-border', hexToHSL(customBorderColor));
248
+ root.style.setProperty('--custom-secondary-font', hexToHSL(secondaryFontColor)); // Auto-calculated subtle variation!
249
+
250
+ log.debug('[ThemeContext] Custom colors applied successfully');
251
+ } catch (error) {
252
+ log.error('[ThemeContext] Error applying custom colors:', error);
253
+ log.error('[ThemeContext] Color values:', {
254
+ customPrimaryColor,
255
+ customBackgroundColor,
256
+ customHeaderColor,
257
+ customSidebarColor,
258
+ customBorderColor,
259
+ });
260
+
261
+ // Disable custom colors on error to prevent cascading failures
262
+ setUseCustomColors(false);
263
+ root.removeAttribute('data-custom-colors');
264
+ }
265
+ } else {
266
+ root.removeAttribute('data-custom-colors');
267
+ root.style.removeProperty('--custom-primary');
268
+ root.style.removeProperty('--custom-primary-text');
269
+ root.style.removeProperty('--custom-primary-readable');
270
+ root.style.removeProperty('--custom-background');
271
+ root.style.removeProperty('--custom-foreground');
272
+ root.style.removeProperty('--custom-header');
273
+ root.style.removeProperty('--custom-header-text');
274
+ root.style.removeProperty('--custom-sidebar');
275
+ root.style.removeProperty('--custom-sidebar-text');
276
+ root.style.removeProperty('--custom-border');
277
+ root.style.removeProperty('--custom-secondary-font');
278
+ }
279
+
280
+ localStorage.setItem('useCustomColors', String(useCustomColors));
281
+ if (useCustomColors) {
282
+ localStorage.setItem('customPrimaryColor', customPrimaryColor);
283
+ localStorage.setItem('customBackgroundColor', customBackgroundColor);
284
+ localStorage.setItem('customHeaderColor', customHeaderColor);
285
+ localStorage.setItem('customSidebarColor', customSidebarColor);
286
+ localStorage.setItem('customBorderColor', customBorderColor);
287
+ }
288
+ }, [
289
+ useCustomColors,
290
+ customPrimaryColor,
291
+ customBackgroundColor,
292
+ customHeaderColor,
293
+ customSidebarColor,
294
+ customBorderColor,
295
+ ]);
296
+
297
+ useEffect(() => {
298
+ const root = window.document.documentElement;
299
+
300
+ // Apply density
301
+ root.setAttribute('data-density', density);
302
+ localStorage.setItem('density', density);
303
+ log.info('Density mode changed', { density });
304
+ }, [density, log]);
305
+
306
+ useEffect(() => {
307
+ const root = window.document.documentElement;
308
+
309
+ // Apply animations
310
+ if (!animations) {
311
+ root.classList.add('no-animations');
312
+ } else {
313
+ root.classList.remove('no-animations');
314
+ }
315
+ localStorage.setItem('animations', String(animations));
316
+ log.debug('Animations toggled', { enabled: animations });
317
+ }, [animations, log]);
318
+
319
+ useEffect(() => {
320
+ const root = window.document.documentElement;
321
+
322
+ // Apply blur effects
323
+ if (!blur) {
324
+ root.classList.add('no-blur');
325
+ } else {
326
+ root.classList.remove('no-blur');
327
+ }
328
+ localStorage.setItem('blur', String(blur));
329
+ log.debug('Blur effects toggled', { enabled: blur });
330
+ }, [blur, log]);
331
+
332
+ useEffect(() => {
333
+ const root = window.document.documentElement;
334
+
335
+ // Apply reduce motion preference
336
+ if (reduceMotion) {
337
+ root.classList.add('reduce-motion');
338
+ } else {
339
+ root.classList.remove('reduce-motion');
340
+ }
341
+ localStorage.setItem('reduceMotion', String(reduceMotion));
342
+ log.debug('Reduce motion toggled', { enabled: reduceMotion });
343
+ }, [reduceMotion, log]);
344
+
345
+ // PERFORMANCE FIX: Apply typography settings with requestAnimationFrame
346
+ // Batches all 6 CSS updates into a single frame to prevent layout thrashing
347
+ useEffect(() => {
348
+ // Use requestAnimationFrame to batch DOM updates
349
+ const frameId = requestAnimationFrame(() => {
350
+ const root = window.document.documentElement;
351
+
352
+ // Batch all CSS custom property updates in one frame
353
+ root.style.setProperty('--custom-font-size', `${fontSize}px`);
354
+ root.style.setProperty('--custom-font-family', fontFamily);
355
+ root.style.setProperty('--custom-font-weight', fontWeight);
356
+ root.style.setProperty('--custom-font-style', fontStyle);
357
+ root.style.setProperty('--custom-letter-spacing', `${letterSpacing}em`);
358
+ root.style.setProperty('--custom-line-height', String(lineHeight));
359
+
360
+ // Persist to localStorage after DOM updates complete
361
+ localStorage.setItem('fontSize', String(fontSize));
362
+ localStorage.setItem('fontFamily', fontFamily);
363
+ localStorage.setItem('fontWeight', fontWeight);
364
+ localStorage.setItem('fontStyle', fontStyle);
365
+ localStorage.setItem('letterSpacing', String(letterSpacing));
366
+ localStorage.setItem('lineHeight', String(lineHeight));
367
+ });
368
+
369
+ // Cleanup: cancel scheduled frame if component unmounts or dependencies change
370
+ return () => cancelAnimationFrame(frameId);
371
+ }, [fontSize, fontFamily, fontWeight, fontStyle, letterSpacing, lineHeight]);
372
+
373
+ return (
374
+ <ThemeContext.Provider
375
+ value={{
376
+ theme,
377
+ setTheme,
378
+ resolvedTheme,
379
+ accentColor,
380
+ setAccentColor,
381
+ customAccentColor,
382
+ setCustomAccentColor,
383
+ customPrimaryColor,
384
+ setCustomPrimaryColor,
385
+ customBackgroundColor,
386
+ setCustomBackgroundColor,
387
+ customHeaderColor,
388
+ setCustomHeaderColor,
389
+ customSidebarColor,
390
+ setCustomSidebarColor,
391
+ customBorderColor,
392
+ setCustomBorderColor,
393
+ useCustomColors,
394
+ setUseCustomColors,
395
+ density,
396
+ setDensity,
397
+ animations,
398
+ setAnimations,
399
+ blur,
400
+ setBlur,
401
+ reduceMotion,
402
+ setReduceMotion,
403
+ fontSize,
404
+ setFontSize,
405
+ fontFamily,
406
+ setFontFamily,
407
+ fontWeight,
408
+ setFontWeight,
409
+ fontStyle,
410
+ setFontStyle,
411
+ letterSpacing,
412
+ setLetterSpacing,
413
+ lineHeight,
414
+ setLineHeight,
415
+ }}
416
+ >
417
+ {children}
418
+ </ThemeContext.Provider>
419
+ );
420
+ }
421
+
422
+ export function useTheme() {
423
+ const context = useContext(ThemeContext);
424
+ if (context === undefined) {
425
+ throw new Error('useTheme must be used within a ThemeProvider');
426
+ }
427
+ return context;
428
+ }