meno-core 1.0.0

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 (231) hide show
  1. package/bin/cli.ts +281 -0
  2. package/build-static.ts +298 -0
  3. package/bunfig.toml +39 -0
  4. package/entries/client-router.tsx +111 -0
  5. package/entries/server-router.tsx +71 -0
  6. package/lib/client/ClientInitializer.test.ts +9 -0
  7. package/lib/client/ClientInitializer.test.ts.skip +92 -0
  8. package/lib/client/ClientInitializer.ts +60 -0
  9. package/lib/client/ErrorBoundary.test.tsx +595 -0
  10. package/lib/client/ErrorBoundary.tsx +230 -0
  11. package/lib/client/componentRegistry.test.ts +165 -0
  12. package/lib/client/componentRegistry.ts +18 -0
  13. package/lib/client/contexts/ThemeContext.tsx +73 -0
  14. package/lib/client/core/ComponentBuilder.test.ts +677 -0
  15. package/lib/client/core/ComponentBuilder.ts +660 -0
  16. package/lib/client/core/ComponentRenderer.test.tsx +176 -0
  17. package/lib/client/core/ComponentRenderer.tsx +83 -0
  18. package/lib/client/core/cmsTemplateProcessor.ts +129 -0
  19. package/lib/client/elementRegistry.ts +81 -0
  20. package/lib/client/hmr/HMRManager.tsx +179 -0
  21. package/lib/client/hmr/index.ts +5 -0
  22. package/lib/client/hmrWebSocket.test.ts +9 -0
  23. package/lib/client/hmrWebSocket.ts +250 -0
  24. package/lib/client/hooks/useColorVariables.test.ts +166 -0
  25. package/lib/client/hooks/useColorVariables.ts +249 -0
  26. package/lib/client/hooks/usePropertyAutocomplete.test.ts +9 -0
  27. package/lib/client/hooks/usePropertyAutocomplete.ts +40 -0
  28. package/lib/client/hydration/HydrationUtils.test.ts +154 -0
  29. package/lib/client/hydration/HydrationUtils.ts +35 -0
  30. package/lib/client/i18nConfigService.test.ts +74 -0
  31. package/lib/client/i18nConfigService.ts +78 -0
  32. package/lib/client/index.ts +56 -0
  33. package/lib/client/navigation.test.ts +441 -0
  34. package/lib/client/navigation.ts +23 -0
  35. package/lib/client/responsiveStyleResolver.test.ts +491 -0
  36. package/lib/client/responsiveStyleResolver.ts +184 -0
  37. package/lib/client/routing/RouteLoader.test.ts +635 -0
  38. package/lib/client/routing/RouteLoader.ts +347 -0
  39. package/lib/client/routing/Router.tsx +382 -0
  40. package/lib/client/scripts/ScriptExecutor.test.ts +489 -0
  41. package/lib/client/scripts/ScriptExecutor.ts +171 -0
  42. package/lib/client/scripts/formHandler.ts +103 -0
  43. package/lib/client/styleProcessor.test.ts +126 -0
  44. package/lib/client/styleProcessor.ts +92 -0
  45. package/lib/client/styles/StyleInjector.test.ts +354 -0
  46. package/lib/client/styles/StyleInjector.ts +154 -0
  47. package/lib/client/templateEngine.test.ts +660 -0
  48. package/lib/client/templateEngine.ts +667 -0
  49. package/lib/client/theme.test.ts +173 -0
  50. package/lib/client/theme.ts +159 -0
  51. package/lib/client/utils/toast.ts +46 -0
  52. package/lib/server/createServer.ts +170 -0
  53. package/lib/server/cssGenerator.test.ts +172 -0
  54. package/lib/server/cssGenerator.ts +58 -0
  55. package/lib/server/fileWatcher.ts +134 -0
  56. package/lib/server/index.ts +55 -0
  57. package/lib/server/jsonLoader.test.ts +103 -0
  58. package/lib/server/jsonLoader.ts +350 -0
  59. package/lib/server/middleware/cors.test.ts +177 -0
  60. package/lib/server/middleware/cors.ts +69 -0
  61. package/lib/server/middleware/errorHandler.test.ts +208 -0
  62. package/lib/server/middleware/errorHandler.ts +63 -0
  63. package/lib/server/middleware/index.ts +9 -0
  64. package/lib/server/middleware/logger.test.ts +233 -0
  65. package/lib/server/middleware/logger.ts +99 -0
  66. package/lib/server/pageCache.test.ts +167 -0
  67. package/lib/server/pageCache.ts +97 -0
  68. package/lib/server/projectContext.ts +51 -0
  69. package/lib/server/providers/fileSystemCMSProvider.test.ts +292 -0
  70. package/lib/server/providers/fileSystemCMSProvider.ts +227 -0
  71. package/lib/server/providers/fileSystemPageProvider.ts +83 -0
  72. package/lib/server/routes/api/cms.test.ts +177 -0
  73. package/lib/server/routes/api/cms.ts +82 -0
  74. package/lib/server/routes/api/colors.ts +59 -0
  75. package/lib/server/routes/api/components.ts +70 -0
  76. package/lib/server/routes/api/config.test.ts +9 -0
  77. package/lib/server/routes/api/config.ts +28 -0
  78. package/lib/server/routes/api/core-routes.ts +182 -0
  79. package/lib/server/routes/api/functions.ts +170 -0
  80. package/lib/server/routes/api/index.ts +69 -0
  81. package/lib/server/routes/api/pages.ts +95 -0
  82. package/lib/server/routes/api/shared.test.ts +81 -0
  83. package/lib/server/routes/api/shared.ts +31 -0
  84. package/lib/server/routes/editor.test.ts +9 -0
  85. package/lib/server/routes/index.ts +104 -0
  86. package/lib/server/routes/pages.ts +161 -0
  87. package/lib/server/routes/static.ts +107 -0
  88. package/lib/server/services/ColorService.ts +193 -0
  89. package/lib/server/services/cmsService.test.ts +388 -0
  90. package/lib/server/services/cmsService.ts +296 -0
  91. package/lib/server/services/componentService.test.ts +276 -0
  92. package/lib/server/services/componentService.ts +346 -0
  93. package/lib/server/services/configService.ts +156 -0
  94. package/lib/server/services/fileWatcherService.ts +67 -0
  95. package/lib/server/services/index.ts +10 -0
  96. package/lib/server/services/pageService.test.ts +258 -0
  97. package/lib/server/services/pageService.ts +240 -0
  98. package/lib/server/ssrRenderer.test.ts +1005 -0
  99. package/lib/server/ssrRenderer.ts +878 -0
  100. package/lib/server/utilityClassGenerator.ts +11 -0
  101. package/lib/server/utils/index.ts +5 -0
  102. package/lib/server/utils/jsonLineMapper.test.ts +100 -0
  103. package/lib/server/utils/jsonLineMapper.ts +166 -0
  104. package/lib/server/validateStyleCoverage.test.ts +9 -0
  105. package/lib/server/validateStyleCoverage.ts +167 -0
  106. package/lib/server/websocketManager.test.ts +9 -0
  107. package/lib/server/websocketManager.ts +95 -0
  108. package/lib/shared/attributeNodeUtils.test.ts +152 -0
  109. package/lib/shared/attributeNodeUtils.ts +50 -0
  110. package/lib/shared/breakpoints.test.ts +166 -0
  111. package/lib/shared/breakpoints.ts +65 -0
  112. package/lib/shared/colorProperties.test.ts +111 -0
  113. package/lib/shared/colorProperties.ts +40 -0
  114. package/lib/shared/colorVariableUtils.test.ts +319 -0
  115. package/lib/shared/colorVariableUtils.ts +97 -0
  116. package/lib/shared/constants.test.ts +175 -0
  117. package/lib/shared/constants.ts +116 -0
  118. package/lib/shared/cssGeneration.ts +481 -0
  119. package/lib/shared/cssProperties.test.ts +252 -0
  120. package/lib/shared/cssProperties.ts +338 -0
  121. package/lib/shared/elementUtils.test.ts +245 -0
  122. package/lib/shared/elementUtils.ts +90 -0
  123. package/lib/shared/fontLoader.ts +97 -0
  124. package/lib/shared/i18n.test.ts +313 -0
  125. package/lib/shared/i18n.ts +286 -0
  126. package/lib/shared/index.ts +50 -0
  127. package/lib/shared/interfaces/contentProvider.test.ts +9 -0
  128. package/lib/shared/interfaces/contentProvider.ts +121 -0
  129. package/lib/shared/nodeUtils.test.ts +320 -0
  130. package/lib/shared/nodeUtils.ts +220 -0
  131. package/lib/shared/pathArrayUtils.test.ts +315 -0
  132. package/lib/shared/pathArrayUtils.ts +17 -0
  133. package/lib/shared/pathUtils.test.ts +260 -0
  134. package/lib/shared/pathUtils.ts +244 -0
  135. package/lib/shared/paths/Path.test.ts +74 -0
  136. package/lib/shared/paths/Path.ts +23 -0
  137. package/lib/shared/paths/PathConverter.test.ts +232 -0
  138. package/lib/shared/paths/PathConverter.ts +141 -0
  139. package/lib/shared/paths/PathUtils.ts +290 -0
  140. package/lib/shared/paths/PathValidator.test.ts +193 -0
  141. package/lib/shared/paths/PathValidator.ts +53 -0
  142. package/lib/shared/paths/index.ts +48 -0
  143. package/lib/shared/propResolver.test.ts +639 -0
  144. package/lib/shared/propResolver.ts +124 -0
  145. package/lib/shared/registry/BaseNodeTypeRegistry.test.ts +190 -0
  146. package/lib/shared/registry/BaseNodeTypeRegistry.ts +200 -0
  147. package/lib/shared/registry/ClientNodeTypeRegistry.ts +34 -0
  148. package/lib/shared/registry/ClientRegistry.test.ts +26 -0
  149. package/lib/shared/registry/ClientRegistry.ts +15 -0
  150. package/lib/shared/registry/ComponentRegistry.test.ts +293 -0
  151. package/lib/shared/registry/ComponentRegistry.ts +100 -0
  152. package/lib/shared/registry/NodeTypeDefinition.ts +198 -0
  153. package/lib/shared/registry/NodeTypeManager.ts +94 -0
  154. package/lib/shared/registry/RegistryManager.test.ts +58 -0
  155. package/lib/shared/registry/RegistryManager.ts +60 -0
  156. package/lib/shared/registry/SSRNodeTypeRegistry.ts +33 -0
  157. package/lib/shared/registry/SSRRegistry.test.ts +26 -0
  158. package/lib/shared/registry/SSRRegistry.ts +15 -0
  159. package/lib/shared/registry/createNodeType.ts +175 -0
  160. package/lib/shared/registry/defineNodeType.ts +73 -0
  161. package/lib/shared/registry/fieldPresets.ts +109 -0
  162. package/lib/shared/registry/index.ts +50 -0
  163. package/lib/shared/registry/nodeTypes/ComponentInstanceNodeType.ts +71 -0
  164. package/lib/shared/registry/nodeTypes/EmbedNodeType.ts +61 -0
  165. package/lib/shared/registry/nodeTypes/HtmlNodeType.ts +88 -0
  166. package/lib/shared/registry/nodeTypes/LocaleListNodeType.ts +66 -0
  167. package/lib/shared/registry/nodeTypes/ObjectLinkNodeType.ts +75 -0
  168. package/lib/shared/registry/nodeTypes/SlotMarkerType.ts +49 -0
  169. package/lib/shared/registry/nodeTypes/TextNodeType.ts +52 -0
  170. package/lib/shared/registry/nodeTypes/index.ts +75 -0
  171. package/lib/shared/responsiveScaling.test.ts +268 -0
  172. package/lib/shared/responsiveScaling.ts +194 -0
  173. package/lib/shared/responsiveStyleUtils.test.ts +300 -0
  174. package/lib/shared/responsiveStyleUtils.ts +139 -0
  175. package/lib/shared/slugTranslator.test.ts +325 -0
  176. package/lib/shared/slugTranslator.ts +177 -0
  177. package/lib/shared/styleNodeUtils.test.ts +132 -0
  178. package/lib/shared/styleNodeUtils.ts +102 -0
  179. package/lib/shared/styleUtils.test.ts +238 -0
  180. package/lib/shared/styleUtils.ts +63 -0
  181. package/lib/shared/themeDefaults.test.ts +113 -0
  182. package/lib/shared/themeDefaults.ts +103 -0
  183. package/lib/shared/tree/PathBuilder.ts +383 -0
  184. package/lib/shared/treePathUtils.test.ts +539 -0
  185. package/lib/shared/treePathUtils.ts +339 -0
  186. package/lib/shared/types/api.ts +58 -0
  187. package/lib/shared/types/cms.ts +95 -0
  188. package/lib/shared/types/colors.ts +45 -0
  189. package/lib/shared/types/components.ts +121 -0
  190. package/lib/shared/types/errors.test.ts +103 -0
  191. package/lib/shared/types/errors.ts +69 -0
  192. package/lib/shared/types/index.ts +96 -0
  193. package/lib/shared/types/nodes.ts +20 -0
  194. package/lib/shared/types/rendering.ts +61 -0
  195. package/lib/shared/types/styles.ts +38 -0
  196. package/lib/shared/types.ts +11 -0
  197. package/lib/shared/utilityClassConfig.ts +287 -0
  198. package/lib/shared/utilityClassMapper.test.ts +140 -0
  199. package/lib/shared/utilityClassMapper.ts +229 -0
  200. package/lib/shared/utils/fileUtils.test.ts +99 -0
  201. package/lib/shared/utils/fileUtils.ts +56 -0
  202. package/lib/shared/utils.test.ts +261 -0
  203. package/lib/shared/utils.ts +84 -0
  204. package/lib/shared/validation/index.ts +7 -0
  205. package/lib/shared/validation/propValidator.test.ts +178 -0
  206. package/lib/shared/validation/propValidator.ts +238 -0
  207. package/lib/shared/validation/schemas.test.ts +177 -0
  208. package/lib/shared/validation/schemas.ts +401 -0
  209. package/lib/shared/validation/validators.test.ts +109 -0
  210. package/lib/shared/validation/validators.ts +304 -0
  211. package/lib/test-utils/dom-setup.ts +55 -0
  212. package/lib/test-utils/factories/ConsoleMockFactory.ts +200 -0
  213. package/lib/test-utils/factories/DomMockFactory.ts +487 -0
  214. package/lib/test-utils/factories/EventMockFactory.ts +244 -0
  215. package/lib/test-utils/factories/FetchMockFactory.ts +210 -0
  216. package/lib/test-utils/factories/ServerMockFactory.ts +223 -0
  217. package/lib/test-utils/factories/StoreMockFactory.ts +370 -0
  218. package/lib/test-utils/factories/index.ts +11 -0
  219. package/lib/test-utils/fixtures.ts +134 -0
  220. package/lib/test-utils/helpers/asyncHelpers.test.ts +112 -0
  221. package/lib/test-utils/helpers/asyncHelpers.ts +196 -0
  222. package/lib/test-utils/helpers/index.ts +6 -0
  223. package/lib/test-utils/helpers.test.ts +73 -0
  224. package/lib/test-utils/helpers.ts +90 -0
  225. package/lib/test-utils/index.ts +17 -0
  226. package/lib/test-utils/mockFactories.ts +92 -0
  227. package/lib/test-utils/mocks.ts +341 -0
  228. package/package.json +38 -0
  229. package/templates/index-router.html +34 -0
  230. package/tsconfig.json +14 -0
  231. package/vite.config.ts +43 -0
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Router Component
3
+ *
4
+ * Main router component that handles routing, state management, and rendering.
5
+ * Uses all extracted modules for a clean, modular architecture.
6
+ *
7
+ * Responsibilities:
8
+ * - Route loading and navigation
9
+ * - Component tree state management
10
+ * - SSR hydration detection
11
+ * - HMR integration
12
+ * - Loading and error states
13
+ * - Viewport width tracking for responsive styles
14
+ */
15
+
16
+ import { createElement as h, useState, useEffect, useRef, useCallback, useMemo } from "react";
17
+ import type { ReactElement } from "react";
18
+ import { renderPage } from "../core/ComponentRenderer";
19
+ import { RouteLoader } from "./RouteLoader";
20
+ import { detectSSRContent, getInitialLoadingState } from "../hydration/HydrationUtils";
21
+ import { HMRManager } from "../hmr/HMRManager";
22
+ import { createNavigationHandler } from "../navigation";
23
+ import { initializeBreakpoints } from "../responsiveStyleResolver";
24
+ import { elementRegistry } from "../elementRegistry";
25
+ import { initializeClient, setupEventHandlers } from "../ClientInitializer";
26
+ import type { ComponentNode, I18nConfig } from "../../shared/types";
27
+ import { parseLocaleFromPath, setStoredLocale, DEFAULT_I18N_CONFIG } from "../../shared/i18n";
28
+ import { fetchI18nConfig, setI18nConfig as setCachedI18nConfig } from "../i18nConfigService";
29
+ import { IFRAME_MESSAGE_TYPES } from "../../shared/constants";
30
+
31
+ /**
32
+ * Router component props
33
+ */
34
+ export interface RouterProps {
35
+ /**
36
+ * Initial path (optional, defaults to window.location.pathname)
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <Router initialPath="/about" />
41
+ * ```
42
+ */
43
+ initialPath?: string;
44
+ }
45
+
46
+ /**
47
+ * Router Component
48
+ *
49
+ * Main router component that handles client-side routing, page loading, and rendering.
50
+ * Manages component tree state, loading states, and integrates with HMR for hot reloading.
51
+ *
52
+ * Features:
53
+ * - Automatic route loading on path changes
54
+ * - SSR hydration support
55
+ * - Loading state management with previous content preservation
56
+ * - 404 handling with available pages list
57
+ * - Viewport width tracking for responsive styles
58
+ * - HMR integration for development
59
+ *
60
+ * @param props - Router configuration props
61
+ * @returns ReactElement representing the router and current page
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * // Basic usage
66
+ * <Router />
67
+ *
68
+ * // With custom initial path
69
+ * <Router initialPath="/custom" />
70
+ * ```
71
+ */
72
+ export function Router(props: RouterProps = {}): ReactElement {
73
+ const { initialPath = window.location.pathname } = props;
74
+ const hasSSRContent = detectSSRContent();
75
+
76
+ // Initialize client services (created once on mount)
77
+ const services = useMemo(() => initializeClient(), []);
78
+
79
+ // Setup event handlers on mount
80
+ useEffect(() => {
81
+ const cleanup = setupEventHandlers(services);
82
+
83
+ // Cleanup on unmount
84
+ return cleanup;
85
+ }, [services]);
86
+
87
+ // State management
88
+ const [componentTree, setComponentTree] = useState<ComponentNode | ComponentNode[] | null>(null);
89
+ // Keep previous componentTree to show during transitions
90
+ const [previousComponentTree, setPreviousComponentTree] = useState<ComponentNode | ComponentNode[] | null>(null);
91
+ // Ref to track current componentTree for closure access (needed for capturing current value in async functions)
92
+ const componentTreeRef = useRef<ComponentNode | ComponentNode[] | null>(null);
93
+ const [loading, setLoading] = useState(getInitialLoadingState(hasSSRContent));
94
+ const [currentPath, setCurrentPath] = useState(initialPath);
95
+ const [availablePages, setAvailablePages] = useState<string[]>([]);
96
+ const [showNotFound, setShowNotFound] = useState(false);
97
+ const [viewportWidth, setViewportWidth] = useState<number>(
98
+ typeof window !== 'undefined' ? window.innerWidth : 1920
99
+ );
100
+ const [i18nConfig, setI18nConfig] = useState<I18nConfig>(DEFAULT_I18N_CONFIG);
101
+ const [currentLocale, setCurrentLocale] = useState<string>(DEFAULT_I18N_CONFIG.defaultLocale);
102
+ const [cmsContext, setCmsContext] = useState<Record<string, unknown> | null>(null);
103
+ const [cmsLocale, setCmsLocale] = useState<string | null>(null);
104
+ const [awaitingCmsContext, setAwaitingCmsContext] = useState(false);
105
+
106
+ // Create RouteLoader instance
107
+ const routeLoader = useRef(new RouteLoader({
108
+ componentRegistry: services.componentRegistry,
109
+ onLoadStart: () => {
110
+ setLoading(true);
111
+ setShowNotFound(false);
112
+ // Save current componentTree as previous before loading
113
+ if (componentTreeRef.current) {
114
+ setPreviousComponentTree(componentTreeRef.current);
115
+ }
116
+ },
117
+ onLoadComplete: (tree) => {
118
+ setComponentTree(tree);
119
+ setLoading(false);
120
+ setShowNotFound(false);
121
+ // Clear previous componentTree now that we have new content
122
+ setPreviousComponentTree(null);
123
+ },
124
+ onLoadError: (error) => {
125
+ setLoading(false);
126
+ setShowNotFound(false);
127
+ setPreviousComponentTree(null);
128
+ },
129
+ onNotFound: () => setShowNotFound(true),
130
+ onPagesLoaded: (pages) => setAvailablePages(pages),
131
+ onLocaleDetected: (locale) => setCurrentLocale(locale),
132
+ })).current;
133
+
134
+ // Keep ref in sync with state
135
+ useEffect(() => {
136
+ componentTreeRef.current = componentTree;
137
+ }, [componentTree]);
138
+
139
+ // Initialize breakpoints on mount
140
+ useEffect(() => {
141
+ initializeBreakpoints();
142
+ }, []);
143
+
144
+ // Load i18n config on mount using shared service
145
+ useEffect(() => {
146
+ fetchI18nConfig().then(config => {
147
+ setI18nConfig(config);
148
+ setCachedI18nConfig(config);
149
+ // Update RouteLoader with i18n config so it can extract locale from paths
150
+ routeLoader.setI18nConfig(config);
151
+ // Extract locale from current path
152
+ const { locale } = parseLocaleFromPath(currentPath, config);
153
+ setCurrentLocale(locale);
154
+ setStoredLocale(locale); // Persist preference
155
+ });
156
+ }, []);
157
+
158
+ // Update locale when path changes
159
+ useEffect(() => {
160
+ const { locale } = parseLocaleFromPath(currentPath, i18nConfig);
161
+ setCurrentLocale(locale);
162
+ setStoredLocale(locale); // Persist preference
163
+ }, [currentPath, i18nConfig]);
164
+
165
+ // Listen to viewport resize to update responsive styles
166
+ useEffect(() => {
167
+ if (typeof window === 'undefined') return;
168
+
169
+ const handleResize = () => {
170
+ setViewportWidth(window.innerWidth);
171
+ };
172
+
173
+ window.addEventListener('resize', handleResize);
174
+ return () => window.removeEventListener('resize', handleResize);
175
+ }, []);
176
+
177
+ // Listen for CMS context updates from parent window (editor)
178
+ useEffect(() => {
179
+ if (typeof window === 'undefined') return;
180
+
181
+ const handleMessage = (event: MessageEvent) => {
182
+ if (event.data?.type === IFRAME_MESSAGE_TYPES.CMS_CONTEXT_UPDATE) {
183
+ setCmsContext(event.data.cmsItem);
184
+ setCmsLocale(event.data.locale || null);
185
+ setAwaitingCmsContext(false); // Context received, ready to render
186
+ }
187
+ };
188
+
189
+ window.addEventListener('message', handleMessage);
190
+ return () => window.removeEventListener('message', handleMessage);
191
+ }, []);
192
+
193
+ // Request CMS context from parent when iframe is ready
194
+ useEffect(() => {
195
+ if (typeof window === 'undefined') return;
196
+ if (window.parent === window) return; // Not in iframe
197
+
198
+ setAwaitingCmsContext(true); // Mark that we're waiting for context
199
+ window.parent.postMessage({
200
+ type: IFRAME_MESSAGE_TYPES.CMS_CONTEXT_REQUEST
201
+ }, '*');
202
+
203
+ // Fallback timeout - if no response in 200ms, render anyway
204
+ // This handles cases where CMSPanel isn't mounted or page has no CMS templates
205
+ const timeoutId = setTimeout(() => {
206
+ setAwaitingCmsContext(false);
207
+ }, 200);
208
+
209
+ return () => clearTimeout(timeoutId);
210
+ }, []);
211
+
212
+ // Inject CSS and execute JavaScript after component tree is rendered
213
+ useEffect(() => {
214
+ if (componentTree) {
215
+ // Inject CSS immediately
216
+ services.styleInjector.inject();
217
+
218
+ // Wait for React to render before executing JavaScript
219
+ const timeoutId = setTimeout(() => {
220
+ services.scriptExecutor.execute();
221
+ }, 100);
222
+
223
+ return () => {
224
+ clearTimeout(timeoutId);
225
+ // Don't clear registry here - ref callbacks handle cleanup when elements unmount
226
+ // Clearing here would remove elements before React unmounts them
227
+ };
228
+ } else {
229
+ // Clear registry when tree is cleared (no elements to register)
230
+ elementRegistry.clear();
231
+ }
232
+ }, [componentTree, services]);
233
+
234
+ // Load components function using RouteLoader
235
+ const loadComponents = useCallback(async (path: string) => {
236
+ await routeLoader.loadComponents(path);
237
+ }, []);
238
+
239
+ // Handle navigation
240
+ useEffect(() => {
241
+ const handlePopState = () => {
242
+ const newPath = window.location.pathname;
243
+ setCurrentPath(newPath);
244
+ setShowNotFound(false); // Reset not found state on navigation
245
+ loadComponents(newPath);
246
+ };
247
+
248
+ window.addEventListener('popstate', handlePopState);
249
+ return () => window.removeEventListener('popstate', handlePopState);
250
+ }, [loadComponents]);
251
+
252
+ useEffect(() => {
253
+ // Initial load
254
+ routeLoader.loadPages();
255
+ routeLoader.loadGlobalComponents().then(() => {
256
+ loadComponents(currentPath);
257
+ });
258
+ // eslint-disable-next-line react-hooks/exhaustive-deps
259
+ }, []); // Only run on mount - currentPath handled by separate effect below
260
+
261
+ // Reload when path changes
262
+ useEffect(() => {
263
+ setShowNotFound(false); // Reset not found state when path changes
264
+ loadComponents(currentPath);
265
+ }, [currentPath, loadComponents]);
266
+
267
+ // Cleanup on unmount
268
+ useEffect(() => {
269
+ return () => {
270
+ routeLoader.cancel();
271
+ };
272
+ }, []);
273
+
274
+ // Render logic: prioritize current tree, then previous tree during loading, then loading/not found states
275
+ let pageContent: ReactElement | null = null;
276
+
277
+ if (componentTree && !awaitingCmsContext) {
278
+ // Show current tree when we have it and CMS context is ready (or not needed)
279
+ pageContent = renderPage({
280
+ tree: componentTree,
281
+ currentPath,
282
+ viewportWidth,
283
+ componentBuilder: services.componentBuilder,
284
+ locale: currentLocale,
285
+ i18nConfig,
286
+ cmsContext,
287
+ cmsLocale
288
+ });
289
+ } else if (componentTree && awaitingCmsContext && previousComponentTree) {
290
+ // While waiting for CMS context, show previous tree to avoid blink
291
+ pageContent = renderPage({
292
+ tree: previousComponentTree,
293
+ currentPath,
294
+ viewportWidth,
295
+ componentBuilder: services.componentBuilder,
296
+ locale: currentLocale,
297
+ i18nConfig,
298
+ cmsContext,
299
+ cmsLocale
300
+ });
301
+ } else if (loading || awaitingCmsContext) {
302
+ // Show previous content while loading if available
303
+ if (previousComponentTree) {
304
+ pageContent = renderPage({
305
+ tree: previousComponentTree,
306
+ currentPath,
307
+ viewportWidth,
308
+ componentBuilder: services.componentBuilder,
309
+ locale: currentLocale,
310
+ i18nConfig,
311
+ cmsContext,
312
+ cmsLocale
313
+ });
314
+ } else {
315
+ // Only show loading message if no previous content
316
+ pageContent = h('div', { style: { textAlign: 'center', padding: '40px' } },
317
+ h('h2', null, 'Loading page...')
318
+ );
319
+ }
320
+ } else if (showNotFound) {
321
+ pageContent = h('div', {
322
+ style: {
323
+ textAlign: 'center',
324
+ padding: '40px',
325
+ maxWidth: '800px',
326
+ margin: '0 auto'
327
+ }
328
+ },
329
+ h('h2', null, `Page not found: ${currentPath}`),
330
+ h('p', { style: { marginTop: '16px', marginBottom: '24px', color: '#666' } },
331
+ 'Available pages:'
332
+ ),
333
+ h('ul', {
334
+ style: {
335
+ listStyle: 'none',
336
+ padding: 0,
337
+ display: 'flex',
338
+ flexDirection: 'column',
339
+ gap: '12px',
340
+ alignItems: 'center'
341
+ }
342
+ },
343
+ availablePages.map((page, i) =>
344
+ h('li', { key: i },
345
+ h('a', {
346
+ href: page,
347
+ onClick: createNavigationHandler(page),
348
+ style: {
349
+ color: '#0070f3',
350
+ textDecoration: 'none',
351
+ fontSize: '18px',
352
+ fontWeight: '600'
353
+ }
354
+ }, page === '/' ? 'Home (/)' : page)
355
+ )
356
+ )
357
+ )
358
+ );
359
+ } else if (previousComponentTree) {
360
+ // Keep previous content visible during timeout (waiting to determine if not found)
361
+ pageContent = renderPage({
362
+ tree: previousComponentTree,
363
+ currentPath,
364
+ viewportWidth,
365
+ componentBuilder: services.componentBuilder,
366
+ locale: currentLocale,
367
+ i18nConfig,
368
+ cmsContext,
369
+ cmsLocale
370
+ });
371
+ }
372
+
373
+ // Render HMRManager once at the top level with page content
374
+ return h('div', null,
375
+ h(HMRManager, {
376
+ onReload: loadComponents,
377
+ currentPath: currentPath,
378
+ }),
379
+ pageContent
380
+ );
381
+ }
382
+