slicejs-web-framework 2.3.5 → 2.4.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 (84) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +24 -5
  3. package/Slice/Components/Structural/Controller/Controller.js +156 -154
  4. package/Slice/Components/Structural/Debugger/Debugger.css +619 -619
  5. package/Slice/Components/Structural/Debugger/Debugger.html +72 -72
  6. package/Slice/Components/Structural/Logger/Log.js +10 -10
  7. package/Slice/Components/Structural/StylesManager/StylesManager.js +6 -3
  8. package/Slice/Slice.js +111 -51
  9. package/api/index.js +261 -233
  10. package/api/middleware/securityMiddleware.js +252 -252
  11. package/package.json +37 -37
  12. package/sliceConfig.schema.json +4 -0
  13. package/src/App/index.html +22 -22
  14. package/src/App/index.js +23 -23
  15. package/src/App/style.css +40 -40
  16. package/src/Components/AppComponents/HomePage/HomePage.css +204 -204
  17. package/src/Components/AppComponents/HomePage/HomePage.html +48 -48
  18. package/src/Components/AppComponents/HomePage/HomePage.js +195 -195
  19. package/src/Components/AppComponents/Playground/Playground.css +11 -11
  20. package/src/Components/AppComponents/Playground/Playground.js +111 -111
  21. package/src/Components/Service/FetchManager/FetchManager.js +133 -133
  22. package/src/Components/Service/IndexedDbManager/IndexedDbManager.js +141 -141
  23. package/src/Components/Service/LocalStorageManager/LocalStorageManager.js +45 -45
  24. package/src/Components/Visual/Button/Button.css +47 -47
  25. package/src/Components/Visual/Button/Button.html +5 -5
  26. package/src/Components/Visual/Button/Button.js +92 -92
  27. package/src/Components/Visual/Card/Card.css +68 -68
  28. package/src/Components/Visual/Card/Card.html +7 -7
  29. package/src/Components/Visual/Card/Card.js +107 -107
  30. package/src/Components/Visual/Checkbox/Checkbox.css +87 -87
  31. package/src/Components/Visual/Checkbox/Checkbox.html +8 -8
  32. package/src/Components/Visual/Checkbox/Checkbox.js +86 -86
  33. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.css +129 -129
  34. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.html +3 -3
  35. package/src/Components/Visual/CodeVisualizer/CodeVisualizer.js +259 -259
  36. package/src/Components/Visual/Details/Details.css +70 -70
  37. package/src/Components/Visual/Details/Details.html +9 -9
  38. package/src/Components/Visual/Details/Details.js +76 -76
  39. package/src/Components/Visual/DropDown/DropDown.css +60 -60
  40. package/src/Components/Visual/DropDown/DropDown.html +5 -5
  41. package/src/Components/Visual/DropDown/DropDown.js +63 -63
  42. package/src/Components/Visual/Grid/Grid.css +7 -7
  43. package/src/Components/Visual/Grid/Grid.html +1 -1
  44. package/src/Components/Visual/Grid/Grid.js +57 -57
  45. package/src/Components/Visual/Icon/Icon.css +510 -510
  46. package/src/Components/Visual/Icon/Icon.js +89 -89
  47. package/src/Components/Visual/Icon/slc.json +554 -554
  48. package/src/Components/Visual/Icon/slc.styl +507 -507
  49. package/src/Components/Visual/Icon/slc.svg +1485 -1485
  50. package/src/Components/Visual/Icon/slc.symbol.svg +1058 -1058
  51. package/src/Components/Visual/Input/Input.css +91 -91
  52. package/src/Components/Visual/Input/Input.html +4 -4
  53. package/src/Components/Visual/Input/Input.js +215 -215
  54. package/src/Components/Visual/Layout/Layout.js +49 -49
  55. package/src/Components/Visual/Loading/Loading.css +56 -56
  56. package/src/Components/Visual/Loading/Loading.html +83 -83
  57. package/src/Components/Visual/Loading/Loading.js +38 -38
  58. package/src/Components/Visual/MultiRoute/MultiRoute.js +93 -93
  59. package/src/Components/Visual/Navbar/Navbar.css +115 -115
  60. package/src/Components/Visual/Navbar/Navbar.html +44 -44
  61. package/src/Components/Visual/Navbar/Navbar.js +141 -141
  62. package/src/Components/Visual/NotFound/NotFound.css +116 -116
  63. package/src/Components/Visual/NotFound/NotFound.html +23 -23
  64. package/src/Components/Visual/NotFound/NotFound.js +16 -16
  65. package/src/Components/Visual/Route/Route.js +93 -93
  66. package/src/Components/Visual/Select/Select.css +84 -84
  67. package/src/Components/Visual/Select/Select.html +8 -8
  68. package/src/Components/Visual/Select/Select.js +195 -195
  69. package/src/Components/Visual/Switch/Switch.css +76 -76
  70. package/src/Components/Visual/Switch/Switch.html +8 -8
  71. package/src/Components/Visual/Switch/Switch.js +102 -102
  72. package/src/Components/Visual/TreeItem/TreeItem.css +36 -36
  73. package/src/Components/Visual/TreeItem/TreeItem.html +1 -1
  74. package/src/Components/Visual/TreeItem/TreeItem.js +126 -126
  75. package/src/Components/Visual/TreeView/TreeView.css +8 -8
  76. package/src/Components/Visual/TreeView/TreeView.html +1 -1
  77. package/src/Components/Visual/TreeView/TreeView.js +48 -48
  78. package/src/Styles/sliceStyles.css +34 -34
  79. package/src/Themes/Dark.css +42 -42
  80. package/src/Themes/Light.css +31 -31
  81. package/src/Themes/Slice.css +47 -47
  82. package/src/routes.js +15 -15
  83. package/src/sliceConfig.json +8 -3
  84. package/src/testing.js +887 -887
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Victor Jose Kneider Alnahi and Julio Antonio Graterol Bracho
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Victor Jose Kneider Alnahi and Julio Antonio Graterol Bracho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -99,10 +99,30 @@ _Below is an example of how you can instruct your audience on installing and set
99
99
  ```sh
100
100
  npm run slice:init
101
101
  ```
102
- 3. Start web server
103
- ```sh
104
- npm run slice:start
105
- ```
102
+ 3. Build production output
103
+ ```sh
104
+ npm run slice:build
105
+ ```
106
+ 4. Start production server
107
+ ```sh
108
+ npm run slice:start
109
+ ```
110
+
111
+ Production uses `publicFolders` from `sliceConfig.json` to expose public asset folders like
112
+ `/Themes`, `/Styles`, and `/assets`.
113
+
114
+ Structural framework components are bundled into a dedicated framework bundle during `slice build`
115
+ and served from `/dist/bundles/slice-bundle.framework.js`.
116
+
117
+ ### Documentation Access
118
+
119
+ Slice.js provides an MCP (Model Context Protocol) server for programmatic access to documentation:
120
+
121
+ ```bash
122
+ npx slicejs-mcp
123
+ ```
124
+
125
+ This allows AI assistants and tools to query, search, and retrieve Slice.js documentation seamlessly.
106
126
 
107
127
  <p align="right">(<a href="#readme-top">back to top</a>)</p>
108
128
 
@@ -152,4 +172,3 @@ Distributed under the MIT License. See `LICENSE` for more information.
152
172
  [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
153
173
  [linkedin-url]: https://linkedin.com/in/VKneider
154
174
  [product-screenshot]: readme_images/screenshot.JPG
155
-
@@ -23,23 +23,21 @@ export default class Controller {
23
23
  /**
24
24
  * 📦 Initializes bundle system (called automatically when config is loaded)
25
25
  */
26
- initializeBundles(config = null) {
27
- if (config) {
28
- this.bundleConfig = config;
29
-
30
- // Register critical bundle components if available
31
- if (config.bundles?.critical) {
32
- // The critical bundle should already be loaded, register its components
33
- this.loadedBundles.add('critical');
34
- // Note: Critical bundle registration is handled by the auto-import
35
- }
36
- this.criticalBundleLoaded = true;
37
- } else {
38
- // No bundles available, will use individual component loading
39
- this.bundleConfig = null;
40
- this.criticalBundleLoaded = false;
41
- }
42
- }
26
+ initializeBundles(config = null) {
27
+ if (config) {
28
+ this.bundleConfig = config;
29
+
30
+ // Register critical bundle components if available
31
+ if (config.bundles?.critical) {
32
+ // Critical bundle will be loaded explicitly
33
+ }
34
+ this.criticalBundleLoaded = false;
35
+ } else {
36
+ // No bundles available, will use individual component loading
37
+ this.bundleConfig = null;
38
+ this.criticalBundleLoaded = false;
39
+ }
40
+ }
43
41
 
44
42
  /**
45
43
  * 📦 Loads a bundle by name or category
@@ -49,17 +47,23 @@ export default class Controller {
49
47
  return; // Already loaded
50
48
  }
51
49
 
52
- try {
53
- let bundleInfo = this.bundleConfig?.bundles?.routes?.[bundleName];
50
+ try {
51
+ let bundleInfo = null;
54
52
 
55
- if (!bundleInfo && this.bundleConfig?.bundles?.routes) {
56
- const normalizedName = bundleName?.toLowerCase();
57
- const matchedKey = Object.keys(this.bundleConfig.bundles.routes).find(
58
- (key) => key.toLowerCase() === normalizedName
59
- );
60
- if (matchedKey) {
61
- bundleInfo = this.bundleConfig.bundles.routes[matchedKey];
62
- }
53
+ if (bundleName === 'critical') {
54
+ bundleInfo = this.bundleConfig?.bundles?.critical;
55
+ } else {
56
+ bundleInfo = this.bundleConfig?.bundles?.routes?.[bundleName];
57
+ }
58
+
59
+ if (!bundleInfo && this.bundleConfig?.bundles?.routes && bundleName !== 'critical') {
60
+ const normalizedName = bundleName?.toLowerCase();
61
+ const matchedKey = Object.keys(this.bundleConfig.bundles.routes).find(
62
+ (key) => key.toLowerCase() === normalizedName
63
+ );
64
+ if (matchedKey) {
65
+ bundleInfo = this.bundleConfig.bundles.routes[matchedKey];
66
+ }
63
67
  }
64
68
 
65
69
  if (!bundleInfo) {
@@ -69,6 +73,12 @@ export default class Controller {
69
73
 
70
74
  const bundlePath = `/bundles/${bundleInfo.file}`;
71
75
 
76
+ const integrityOk = await this.verifyBundleIntegrity(bundlePath, bundleInfo.integrity);
77
+ if (!integrityOk) {
78
+ console.warn(`❌ Integrity check failed for bundle ${bundleName}`);
79
+ return;
80
+ }
81
+
72
82
  // Dynamic import of the bundle
73
83
  const bundleModule = await import(bundlePath);
74
84
 
@@ -81,7 +91,48 @@ export default class Controller {
81
91
  } catch (error) {
82
92
  console.warn(`Failed to load bundle ${bundleName}:`, error);
83
93
  }
84
- }
94
+ }
95
+
96
+ /**
97
+ * Verifies bundle integrity by comparing sha256 hash.
98
+ * @param {string} bundlePath
99
+ * @param {string|null} expectedIntegrity
100
+ * @returns {Promise<boolean>}
101
+ */
102
+ async verifyBundleIntegrity(bundlePath, expectedIntegrity) {
103
+ if (!expectedIntegrity) {
104
+ return true;
105
+ }
106
+
107
+ try {
108
+ const response = await fetch(bundlePath, { cache: 'no-store' });
109
+ if (!response.ok) {
110
+ console.warn(`Failed to fetch bundle for integrity check: ${response.status}`);
111
+ return false;
112
+ }
113
+
114
+ const content = await response.text();
115
+ const actualIntegrity = await this.hashSha256(content);
116
+ return actualIntegrity === expectedIntegrity;
117
+ } catch (error) {
118
+ console.warn(`Integrity check error for ${bundlePath}:`, error);
119
+ return false;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Calculates sha256 digest for a string.
125
+ * @param {string} content
126
+ * @returns {Promise<string>}
127
+ */
128
+ async hashSha256(content) {
129
+ const encoder = new TextEncoder();
130
+ const data = encoder.encode(content);
131
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
132
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
133
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
134
+ return `sha256:${hashHex}`;
135
+ }
85
136
 
86
137
  /**
87
138
  * 📦 Registers a bundle's components (called automatically by bundle files)
@@ -237,147 +288,98 @@ export default class Controller {
237
288
  /**
238
289
  * 📦 New bundle registration method (simplified and robust)
239
290
  */
240
- registerBundle(bundle) {
241
- const { components, metadata } = bundle;
291
+ registerBundle(bundle) {
292
+ const validation = this.validateBundle(bundle);
293
+ if (!validation.isValid) {
294
+ console.warn(`❌ Bundle validation failed: ${validation.error}`);
295
+ return;
296
+ }
297
+
298
+ const { components, metadata } = bundle;
242
299
 
243
300
  console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
244
301
 
245
- // Phase 1: Register all templates and CSS first
246
- for (const [componentName, componentData] of Object.entries(components)) {
247
- try {
248
- if (componentData.html !== undefined && !this.templates.has(componentName)) {
249
- const template = document.createElement('template');
250
- template.innerHTML = componentData.html || '';
251
- this.templates.set(componentName, template);
252
- }
302
+ const entries = Object.entries(components);
303
+ const chunkSize = 50;
304
+ let index = 0;
253
305
 
254
- if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
255
- if (window.slice && window.slice.stylesManager) {
256
- window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
257
- this.requestedStyles.add(componentName);
258
- console.log(`🎨 CSS registered for: ${componentName}`);
306
+ const processChunk = () => {
307
+ const sliceEntries = entries.slice(index, index + chunkSize);
308
+
309
+ for (const [componentName, componentData] of sliceEntries) {
310
+ try {
311
+ if (componentData.html !== undefined && !this.templates.has(componentName)) {
312
+ const template = document.createElement('template');
313
+ template.innerHTML = componentData.html || '';
314
+ this.templates.set(componentName, template);
259
315
  }
316
+
317
+ if (componentData.css !== undefined && !this.requestedStyles.has(componentName)) {
318
+ if (window.slice && window.slice.stylesManager) {
319
+ window.slice.stylesManager.registerComponentStyles(componentName, componentData.css || '');
320
+ this.requestedStyles.add(componentName);
321
+ }
322
+ }
323
+
324
+ if (componentData.class && !this.classes.has(componentName)) {
325
+ const registeredName = componentData.isFramework
326
+ ? `Framework/Structural/${componentName}`
327
+ : componentName;
328
+ this.classes.set(registeredName, componentData.class);
329
+ }
330
+ } catch (error) {
331
+ console.warn(`❌ Failed to register component ${componentName}:`, error);
260
332
  }
261
- } catch (error) {
262
- console.warn(`❌ Failed to register assets for ${componentName}:`, error);
263
333
  }
264
- }
265
334
 
266
- // Phase 2: Evaluate all external file dependencies first
267
- const processedDeps = new Set();
268
- for (const [componentName, componentData] of Object.entries(components)) {
269
- if (componentData.externalDependencies) {
270
- for (const [depName, depEntry] of Object.entries(componentData.externalDependencies)) {
271
- const depKey = depName || '';
272
- if (!processedDeps.has(depKey)) {
273
- try {
274
- const depContent = typeof depEntry === 'string' ? depEntry : depEntry.content;
275
- const bindings = typeof depEntry === 'string' ? [] : depEntry.bindings || [];
276
-
277
- const fileBaseName = depKey
278
- ? depKey
279
- .split('/')
280
- .pop()
281
- .replace(/\.[^.]+$/, '')
282
- : '';
283
- const dataName = fileBaseName ? `${fileBaseName}Data` : '';
284
- const exportPrefix = dataName ? `window.${dataName} = ` : '';
285
-
286
- // Process ES6 exports to make the code evaluable
287
- let processedContent = depContent
288
- // Convert named exports: export const varName = ... → window.varName = ...
289
- .replace(/export\s+const\s+(\w+)\s*=\s*/g, 'window.$1 = ')
290
- .replace(/export\s+let\s+(\w+)\s*=\s*/g, 'window.$1 = ')
291
- .replace(/export\s+function\s+(\w+)/g, 'window.$1 = function')
292
- .replace(/export\s+default\s+/g, 'window.defaultExport = ')
293
- // Promote default export to <file>Data for data modules
294
- .replace(/window\.defaultExport\s*=\s*/g, exportPrefix || 'window.defaultExport = ')
295
- // Handle export { var1, var2 } statements
296
- .replace(/export\s*{\s*([^}]+)\s*}/g, (match, exportsStr) => {
297
- const exports = exportsStr.split(',').map((exp) => exp.trim().split(' as ')[0].trim());
298
- return exports.map((varName) => `window.${varName} = ${varName};`).join('\n');
299
- })
300
- // Remove any remaining export keywords
301
- .replace(/^\s*export\s+/gm, '');
335
+ index += chunkSize;
336
+ if (index < entries.length) {
337
+ if (typeof requestIdleCallback === 'function') {
338
+ requestIdleCallback(processChunk);
339
+ } else {
340
+ setTimeout(processChunk, 0);
341
+ }
342
+ }
343
+ };
302
344
 
303
- // Evaluate the processed content
304
- new Function('slice', 'customElements', 'window', 'document', processedContent)(
305
- window.slice,
306
- window.customElements,
307
- window,
308
- window.document
309
- );
310
-
311
- // Apply import bindings to map local identifiers to globals
312
- for (const binding of bindings) {
313
- if (!binding?.localName) continue;
314
-
315
- if (binding.type === 'default') {
316
- if (!window[binding.localName]) {
317
- const fallbackValue =
318
- dataName && window[dataName] !== undefined ? window[dataName] : window.defaultExport;
319
- if (fallbackValue !== undefined) {
320
- window[binding.localName] = fallbackValue;
321
- }
322
- }
323
- }
345
+ processChunk();
324
346
 
325
- if (binding.type === 'named') {
326
- if (!window[binding.localName] && window[binding.importedName] !== undefined) {
327
- window[binding.localName] = window[binding.importedName];
328
- }
329
- }
347
+ console.log(`✅ Bundle registration completed: ${metadata.componentCount} components processed`);
348
+ }
330
349
 
331
- if (binding.type === 'namespace' && !window[binding.localName]) {
332
- const namespace = {};
333
- Object.keys(window).forEach((key) => {
334
- namespace[key] = window[key];
335
- });
336
- window[binding.localName] = namespace;
337
- }
338
- }
350
+ /**
351
+ * Validates bundle structure before registering.
352
+ * @param {object} bundle
353
+ * @returns {{isValid: boolean, error?: string}}
354
+ */
355
+ validateBundle(bundle) {
356
+ if (!bundle || typeof bundle !== 'object') {
357
+ return { isValid: false, error: 'Bundle payload is invalid' };
358
+ }
339
359
 
340
- processedDeps.add(depKey);
341
- console.log(`📄 External dependency loaded: ${depName}`);
342
- } catch (depError) {
343
- console.warn(`⚠️ Failed to load external dependency ${depName}:`, depError);
344
- const preview = typeof depEntry === 'string' ? depEntry : depEntry.content;
345
- console.warn('Original content preview:', preview.substring(0, 200));
346
- }
347
- }
348
- }
349
- }
350
- }
360
+ if (!bundle.metadata || typeof bundle.metadata !== 'object') {
361
+ return { isValid: false, error: 'Bundle metadata missing' };
362
+ }
351
363
 
352
- // Phase 3: Evaluate all component classes (external dependencies are now available)
353
- for (const [componentName, componentData] of Object.entries(components)) {
354
- if (componentData.js && !this.classes.has(componentName)) {
355
- try {
356
- // Simple evaluation
357
- const componentClass = new Function(
358
- 'slice',
359
- 'customElements',
360
- 'window',
361
- 'document',
362
- `
363
- ${componentData.js}
364
- return ${componentName};
365
- `
366
- )(window.slice, window.customElements, window, window.document);
364
+ if (!bundle.components || typeof bundle.components !== 'object') {
365
+ return { isValid: false, error: 'Bundle components missing' };
366
+ }
367
367
 
368
- if (componentClass) {
369
- this.classes.set(componentName, componentClass);
370
- console.log(`📝 Class registered for: ${componentName}`);
371
- }
372
- } catch (error) {
373
- console.warn(`❌ Failed to evaluate class for ${componentName}:`, error);
374
- // Continue with other components instead of failing completely
375
- }
376
- }
377
- }
368
+ if (typeof bundle.metadata.componentCount !== 'number') {
369
+ return { isValid: false, error: 'Bundle metadata missing componentCount' };
370
+ }
378
371
 
379
- console.log(`✅ Bundle registration completed: ${metadata.componentCount} components processed`);
380
- }
372
+ if (bundle.metadata.componentCount !== Object.keys(bundle.components).length) {
373
+ return { isValid: false, error: 'Bundle component count mismatch' };
374
+ }
375
+
376
+ const maxComponents = 5000;
377
+ if (bundle.metadata.componentCount > maxComponents) {
378
+ return { isValid: false, error: 'Bundle component count exceeds limit' };
379
+ }
380
+
381
+ return { isValid: true };
382
+ }
381
383
 
382
384
  /**
383
385
  * 📦 Determines which bundle to load for a component