juxscript 1.0.62 → 1.0.63

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 (141) hide show
  1. package/bin/cli.js +161 -293
  2. package/docs/v2comps/HEADLESS.md +83 -0
  3. package/docs/v2comps/ISOMORPHISM.md +10 -0
  4. package/juxconfig.example.js +63 -58
  5. package/lib/componentsv2/base/BaseEngine.js +258 -0
  6. package/lib/componentsv2/base/BaseEngine.js.map +1 -0
  7. package/lib/componentsv2/base/BaseEngine.ts +303 -0
  8. package/lib/componentsv2/base/BaseSkin.js +108 -0
  9. package/lib/componentsv2/base/BaseSkin.js.map +1 -0
  10. package/lib/componentsv2/base/BaseSkin.ts +137 -0
  11. package/lib/componentsv2/base/GlobalBus.js +56 -0
  12. package/lib/componentsv2/base/GlobalBus.js.map +1 -0
  13. package/lib/componentsv2/base/GlobalBus.ts +60 -0
  14. package/lib/componentsv2/base/State.js +68 -0
  15. package/lib/componentsv2/base/State.js.map +1 -0
  16. package/lib/componentsv2/base/State.ts +62 -0
  17. package/lib/componentsv2/grid/component.js +41 -0
  18. package/lib/componentsv2/grid/component.js.map +1 -0
  19. package/lib/componentsv2/grid/component.ts +67 -0
  20. package/lib/componentsv2/grid/engine.js +73 -0
  21. package/lib/componentsv2/grid/engine.js.map +1 -0
  22. package/lib/componentsv2/grid/engine.ts +110 -0
  23. package/lib/componentsv2/grid/skin.js +95 -0
  24. package/lib/componentsv2/grid/skin.js.map +1 -0
  25. package/lib/componentsv2/grid/skin.ts +105 -0
  26. package/lib/componentsv2/grid/structure.css +58 -0
  27. package/lib/componentsv2/index.js +218 -0
  28. package/lib/componentsv2/index.js.map +1 -0
  29. package/lib/componentsv2/index.ts +253 -0
  30. package/lib/componentsv2/input/component.js +21 -0
  31. package/lib/componentsv2/input/component.js.map +1 -0
  32. package/lib/componentsv2/input/component.ts +28 -0
  33. package/lib/componentsv2/input/engine.js +50 -0
  34. package/lib/componentsv2/input/engine.js.map +1 -0
  35. package/lib/componentsv2/input/engine.ts +76 -0
  36. package/lib/componentsv2/input/skin.js +91 -0
  37. package/lib/componentsv2/input/skin.js.map +1 -0
  38. package/lib/componentsv2/input/skin.ts +91 -0
  39. package/lib/componentsv2/input/structure.css +47 -0
  40. package/lib/componentsv2/list/component.js +83 -0
  41. package/lib/componentsv2/list/component.js.map +1 -0
  42. package/lib/componentsv2/list/component.ts +97 -0
  43. package/lib/componentsv2/list/engine.js +261 -0
  44. package/lib/componentsv2/list/engine.js.map +1 -0
  45. package/lib/componentsv2/list/engine.ts +345 -0
  46. package/lib/componentsv2/list/skin.js +343 -0
  47. package/lib/componentsv2/list/skin.js.map +1 -0
  48. package/lib/componentsv2/list/skin.ts +367 -0
  49. package/lib/componentsv2/list/structure.css +359 -0
  50. package/lib/componentsv2/plugins/ClientSQLitePlugin.js +130 -0
  51. package/lib/componentsv2/plugins/ClientSQLitePlugin.js.map +1 -0
  52. package/lib/componentsv2/plugins/ClientSQLitePlugin.ts +154 -0
  53. package/lib/componentsv2/plugins/IndexedDBPlugin.js +75 -0
  54. package/lib/componentsv2/plugins/IndexedDBPlugin.js.map +1 -0
  55. package/lib/componentsv2/plugins/IndexedDBPlugin.ts +96 -0
  56. package/lib/componentsv2/plugins/LocalStoragePlugin.js +65 -0
  57. package/lib/componentsv2/plugins/LocalStoragePlugin.js.map +1 -0
  58. package/lib/componentsv2/plugins/LocalStoragePlugin.ts +86 -0
  59. package/lib/componentsv2/plugins/ServerSQLitePlugin.js +70 -0
  60. package/lib/componentsv2/plugins/ServerSQLitePlugin.js.map +1 -0
  61. package/lib/componentsv2/plugins/ServerSQLitePlugin.ts +99 -0
  62. package/lib/componentsv2/stubs/ComponentComposition.ts.stub +32 -0
  63. package/lib/componentsv2/stubs/ComponentEngine.ts.stub +36 -0
  64. package/lib/componentsv2/stubs/ComponentSkin.ts.stub +34 -0
  65. package/lib/componentsv2/stubs/ComponentStructure.css.stub +13 -0
  66. package/lib/componentsv2/tools/CreateSkin.js +62 -0
  67. package/lib/componentsv2/tools/DocSpam.js +134 -0
  68. package/lib/componentsv2/tools/FluencyAudit.js +141 -0
  69. package/lib/componentsv2/tools/OptionsAudit.js +177 -0
  70. package/lib/componentsv2/tools/Scaffold.js +140 -0
  71. package/lib/utils/fetch.js +428 -0
  72. package/lib/utils/fetch.js.map +1 -0
  73. package/machinery/build.js +2 -1
  74. package/machinery/compiler.js +200 -37
  75. package/machinery/config.js +93 -6
  76. package/machinery/diagnose.js +72 -0
  77. package/machinery/jux-module-pattern.md +118 -0
  78. package/machinery/server.js +23 -7
  79. package/machinery/verifier.js +143 -0
  80. package/machinery/watcher.js +53 -64
  81. package/package.json +11 -2
  82. package/lib/components/alert.ts +0 -200
  83. package/lib/components/app.ts +0 -258
  84. package/lib/components/badge.ts +0 -101
  85. package/lib/components/base/BaseComponent.ts +0 -417
  86. package/lib/components/base/FormInput.ts +0 -227
  87. package/lib/components/button.ts +0 -178
  88. package/lib/components/card.ts +0 -173
  89. package/lib/components/chart.ts +0 -231
  90. package/lib/components/checkbox.ts +0 -242
  91. package/lib/components/code.ts +0 -123
  92. package/lib/components/container.ts +0 -140
  93. package/lib/components/data.ts +0 -135
  94. package/lib/components/datepicker.ts +0 -234
  95. package/lib/components/dialog.ts +0 -172
  96. package/lib/components/divider.ts +0 -100
  97. package/lib/components/dropdown.ts +0 -186
  98. package/lib/components/element.ts +0 -267
  99. package/lib/components/error-handler.ts +0 -285
  100. package/lib/components/fileupload.ts +0 -309
  101. package/lib/components/grid.ts +0 -291
  102. package/lib/components/guard.ts +0 -92
  103. package/lib/components/heading.ts +0 -96
  104. package/lib/components/helpers.ts +0 -41
  105. package/lib/components/hero.ts +0 -224
  106. package/lib/components/icon.ts +0 -160
  107. package/lib/components/icons.ts +0 -175
  108. package/lib/components/include.ts +0 -440
  109. package/lib/components/input.ts +0 -457
  110. package/lib/components/list.ts +0 -419
  111. package/lib/components/loading.ts +0 -100
  112. package/lib/components/menu.ts +0 -260
  113. package/lib/components/modal.ts +0 -239
  114. package/lib/components/nav.ts +0 -257
  115. package/lib/components/paragraph.ts +0 -97
  116. package/lib/components/progress.ts +0 -139
  117. package/lib/components/radio.ts +0 -278
  118. package/lib/components/req.ts +0 -302
  119. package/lib/components/script.ts +0 -43
  120. package/lib/components/select.ts +0 -252
  121. package/lib/components/sidebar.ts +0 -167
  122. package/lib/components/style.ts +0 -43
  123. package/lib/components/switch.ts +0 -246
  124. package/lib/components/table.ts +0 -1249
  125. package/lib/components/tabs.ts +0 -250
  126. package/lib/components/theme-toggle.ts +0 -300
  127. package/lib/components/token-calculator.ts +0 -313
  128. package/lib/components/tooltip.ts +0 -144
  129. package/lib/components/view.ts +0 -190
  130. package/lib/components/write.ts +0 -272
  131. package/lib/jux.ts +0 -365
  132. package/lib/layouts/default.css +0 -260
  133. package/lib/layouts/figma.css +0 -334
  134. package/lib/reactivity/state.ts +0 -78
  135. package/machinery/bundleAssets.js +0 -0
  136. package/machinery/bundleJux.js +0 -0
  137. package/machinery/bundleVendors.js +0 -0
  138. package/presets/default/all.jux +0 -343
  139. package/presets/default/index.jux +0 -90
  140. package/presets/default/layout.jux +0 -57
  141. package/presets/default/style.css +0 -1612
@@ -4,6 +4,7 @@ import path from 'path';
4
4
  import fs from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { startWatcher } from './watcher.js';
7
+ import { verifyRuntime } from './verifier.js'; // ✅ Import Verifier
7
8
  import { WebSocketServer } from 'ws';
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
@@ -47,7 +48,8 @@ async function tryPort(startPort, maxAttempts = 5, reservedPorts = []) {
47
48
  throw new Error(`Could not find available port after ${maxAttempts} attempts starting from ${startPort}`);
48
49
  }
49
50
 
50
- async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
51
+ // FIX: Added config parameter to serve function signature
52
+ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist', config = {}) {
51
53
  const app = express();
52
54
  const absoluteDistDir = path.resolve(distDir);
53
55
  const projectRoot = path.resolve('.');
@@ -77,6 +79,9 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
77
79
  next();
78
80
  });
79
81
 
82
+ // ✅ SILENCE FAVICON: Return 204 (No Content) to stop browser 404 errors
83
+ app.get('/favicon.ico', (req, res) => res.status(204).end());
84
+
80
85
  // Serve static files
81
86
  app.use(express.static(absoluteDistDir));
82
87
 
@@ -134,17 +139,28 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
134
139
  console.log(`🔌 WebSocket server running at ws://localhost:${availableWsPort}`);
135
140
 
136
141
  // Start HTTP server
137
- server.listen(availableHttpPort, () => {
142
+ server.listen(availableHttpPort, async () => {
138
143
  console.log(`🚀 JUX dev server running at http://localhost:${availableHttpPort}`);
139
144
  console.log(` Serving: ${absoluteDistDir}`);
140
- console.log(` Press Ctrl+C to stop\n`);
145
+ // console.log(` Press Ctrl+C to stop\n`); removed to group logs
146
+
147
+ // ✅ NEW: Post-Startup Runtime Verification
148
+ // Detects "Served HTML as JS" errors immediately
149
+ const isHealthy = await verifyRuntime(availableHttpPort);
150
+ if (!isHealthy) {
151
+ console.error('🛑 Server failed runtime verification. Shutting down due to critical errors.');
152
+ process.exit(1);
153
+ }
141
154
  });
142
155
 
143
156
  // Start file watcher
144
- const juxSource = path.join(projectRoot, 'jux');
157
+ const sourceName = config?.directories?.source || 'jux';
158
+ const juxSource = path.join(projectRoot, sourceName);
159
+
145
160
  if (fs.existsSync(juxSource)) {
146
161
  console.log(`👀 Watching: ${juxSource}\n`);
147
- startWatcher(juxSource, absoluteDistDir, clients);
162
+ // Pass availableWsPort so we can regenerate index.html with correct port on rebuild
163
+ startWatcher(juxSource, absoluteDistDir, clients, config, availableWsPort);
148
164
  }
149
165
 
150
166
  // Graceful shutdown
@@ -161,6 +177,6 @@ async function serve(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
161
177
  return { server, httpPort: availableHttpPort, wsPort: availableWsPort };
162
178
  }
163
179
 
164
- export async function start(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist') {
165
- return serve(httpPort, wsPort, distDir);
180
+ export async function start(httpPort = 3000, wsPort = 3001, distDir = './.jux-dist', config = {}) {
181
+ return serve(httpPort, wsPort, distDir, config);
166
182
  }
@@ -0,0 +1,143 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import http from 'http';
4
+ import * as acorn from 'acorn'; // Needed to parse main.js
5
+
6
+ /**
7
+ * Checks the static `distDir` for critical files and valid import map references.
8
+ * Returns true if healthy, false if broken.
9
+ */
10
+ export function verifyStaticBuild(distDir) {
11
+ // console.log('🕵️ Verifying build integrity...');
12
+ const errors = [];
13
+
14
+ if (!fs.existsSync(distDir)) {
15
+ console.error('❌ Dist dir does not exist');
16
+ return false;
17
+ }
18
+
19
+ // 1. Critical Files Existence
20
+ const critical = ['index.html', 'main.js', 'lib/jux.js'];
21
+ for (const f of critical) {
22
+ if (!fs.existsSync(path.join(distDir, f))) {
23
+ errors.push(`Missing critical artifact: ${f}`);
24
+ }
25
+ }
26
+
27
+ // 2. Import Map Integrity Check
28
+ let importMap = {};
29
+ try {
30
+ const indexPath = path.join(distDir, 'index.html');
31
+ if (fs.existsSync(indexPath)) {
32
+ const html = fs.readFileSync(indexPath, 'utf8');
33
+ const match = html.match(/<script type="importmap">([\s\S]*?)<\/script>/);
34
+
35
+ if (match && match[1]) {
36
+ importMap = JSON.parse(match[1]).imports || {};
37
+ Object.entries(importMap).forEach(([key, url]) => {
38
+ // Skip folders (trailing slash) and CDN links
39
+ if (typeof url === 'string' && !url.endsWith('/') && !url.startsWith('http')) {
40
+ const fsPath = url.replace(/^\//, '').split('?')[0];
41
+ const fullPath = path.join(distDir, fsPath);
42
+
43
+ if (!fs.existsSync(fullPath)) {
44
+ errors.push(`Broken Import Map: "${key}" -> ${url} (File not found)`);
45
+ }
46
+ }
47
+ });
48
+ }
49
+ }
50
+ } catch (e) {
51
+ errors.push(`Import Map Verification Error: ${e.message}`);
52
+ }
53
+
54
+ // 3. Bundle Import Verification (Detect Ghost Dependencies)
55
+ const mainJsPath = path.join(distDir, 'main.js');
56
+ if (fs.existsSync(mainJsPath)) {
57
+ try {
58
+ const code = fs.readFileSync(mainJsPath, 'utf8');
59
+ const ast = acorn.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
60
+
61
+ ast.body.forEach(node => {
62
+ if (node.type === 'ImportDeclaration') {
63
+ const source = node.source.value;
64
+
65
+ // Allow internal relative paths if they exist
66
+ if (source.startsWith('/') || source.startsWith('.')) {
67
+ // basic existence check (simplified)
68
+ return;
69
+ }
70
+
71
+ // Check if bare specifier exists in Import Map
72
+ // e.g. import axios from 'axios' -> must be in map
73
+ if (!importMap[source] && !importMap[source + '/']) {
74
+ errors.push(`Ghost Dependency: '${source}' is imported in main.js but missing from Import Map.`);
75
+ }
76
+ }
77
+ });
78
+ } catch (e) {
79
+ errors.push(`Failed to parse main.js for verification: ${e.message}`);
80
+ }
81
+ }
82
+
83
+ if (errors.length > 0) {
84
+ console.error(`\n❌ BUILD VERIFICATION FAILED (${errors.length} errors):`);
85
+ errors.forEach(e => console.error(` - ${e}`));
86
+ console.log('');
87
+ return false;
88
+ }
89
+
90
+ // console.log(' ✓ Static verification passed');
91
+ return true;
92
+ }
93
+
94
+ /**
95
+ * Pings the running server to ensure correct MIME types and Route availability.
96
+ * Detects "serving JS as HTML" (MIME type mismatch) errors.
97
+ */
98
+ export function verifyRuntime(port) {
99
+ return new Promise((resolve) => {
100
+ console.log('🩺 Performing runtime diagnostics...');
101
+ const errors = [];
102
+
103
+ // Helper to check a single URL
104
+ const check = (urlPath, expectedMime) => new Promise(r => {
105
+ const req = http.get(`http://localhost:${port}${urlPath}`, res => {
106
+ // Check Status
107
+ if (res.statusCode !== 200 && res.statusCode !== 304) {
108
+ errors.push(`GET ${urlPath} : Status ${res.statusCode} (Expected 200)`);
109
+ } else {
110
+ // Check MIME type
111
+ const ct = res.headers['content-type'] || '';
112
+ if (expectedMime && !ct.includes(expectedMime)) {
113
+ errors.push(`GET ${urlPath} : Bad MIME type. Got "${ct}", expected "${expectedMime}". (Did you get index.html fallback?)`);
114
+ }
115
+ }
116
+ res.resume(); // Consume stream
117
+ r();
118
+ });
119
+
120
+ req.on('error', e => {
121
+ errors.push(`GET ${urlPath} : Connection failed - ${e.message}`);
122
+ r();
123
+ });
124
+ });
125
+
126
+ // Run checks in parallel
127
+ Promise.all([
128
+ check('/', 'text/html'), // Route should be HTML
129
+ check('/main.js', 'application/javascript'), // Bundle must be JS
130
+ check('/lib/jux.js', 'application/javascript') // Library must be JS
131
+ ]).then(() => {
132
+ if (errors.length > 0) {
133
+ console.error(`\n💥 RUNTIME VERIFICATION FAILED (` + errors.length + ` errors):`);
134
+ errors.forEach(e => console.error(` - ${e}`));
135
+ console.log('');
136
+ resolve(false);
137
+ } else {
138
+ console.log(' ✓ Runtime health healthy (Routes & MIME types OK)\n');
139
+ resolve(true);
140
+ }
141
+ });
142
+ });
143
+ }
@@ -5,6 +5,7 @@ import {
5
5
  bundleJuxFilesToRouter,
6
6
  generateIndexHtml
7
7
  } from './compiler.js';
8
+ import { verifyStaticBuild } from './verifier.js'; // ✅ Import Verifier
8
9
 
9
10
  let isRebuilding = false;
10
11
  let rebuildQueued = false;
@@ -36,7 +37,7 @@ function findJuxFiles(dir, fileList = []) {
36
37
  /**
37
38
  * Full rebuild of the entire bundle
38
39
  */
39
- async function fullRebuild(juxSource, distDir) {
40
+ async function fullRebuild(juxSource, distDir, config, wsPort) {
40
41
  const startTime = performance.now(); // ✅ Start timing
41
42
  console.log('\n🔄 Rebuilding bundle...');
42
43
 
@@ -44,68 +45,29 @@ async function fullRebuild(juxSource, distDir) {
44
45
  // Find all .jux files
45
46
  const projectJuxFiles = findJuxFiles(juxSource);
46
47
  console.log(` Found ${projectJuxFiles.length} .jux file(s)`);
47
-
48
48
  // ✅ Time the bundling step
49
49
  const bundleStartTime = performance.now();
50
-
51
50
  // Bundle all files
52
- const mainJsFilename = await bundleJuxFilesToRouter(juxSource, distDir, {
53
- routePrefix: ''
51
+
52
+ // ✅ Return bundleResult with vendor info
53
+ const bundleResult = await bundleJuxFilesToRouter(juxSource, distDir, {
54
+ routePrefix: '',
55
+ config
54
56
  });
55
57
 
56
58
  const bundleTime = performance.now() - bundleStartTime;
57
-
58
- // Time the route generation step
59
- const routeStartTime = performance.now();
60
-
61
- // Generate routes for index.html
62
- const routes = projectJuxFiles.map(juxFile => {
63
- const relativePath = path.relative(juxSource, juxFile);
64
- const parsedPath = path.parse(relativePath);
65
-
66
- const rawFunctionName = parsedPath.dir
67
- ? `${parsedPath.dir.replace(/\//g, '_')}_${parsedPath.name}`
68
- : parsedPath.name;
69
-
70
- const functionName = rawFunctionName
71
- .replace(/[-_]/g, ' ')
72
- .split(' ')
73
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
74
- .join('');
75
-
76
- const routePath = '/' + (parsedPath.dir ? `${parsedPath.dir}/` : '') + parsedPath.name;
77
-
78
- return {
79
- path: routePath.replace(/\/+/g, '/'),
80
- functionName
81
- };
59
+ // ✅ Generate unified index.html (INJECT HOT RELOAD SCRIPT)
60
+ generateIndexHtml(distDir, bundleResult, {
61
+ isDev: true,
62
+ wsPort: wsPort || 3001
82
63
  });
83
64
 
84
- // Generate index.html
85
- generateIndexHtml(distDir, routes, mainJsFilename);
86
-
87
- const routeTime = performance.now() - routeStartTime;
88
- const totalTime = performance.now() - startTime;
89
-
90
- // ✅ Pretty-print timing breakdown
91
- console.log(`\n⏱️ Rebuild Performance:`);
92
- console.log(` Bundle generation: ${bundleTime.toFixed(0)}ms`);
93
- console.log(` Route/index.html: ${routeTime.toFixed(0)}ms`);
94
- console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
95
- console.log(` Total rebuild: ${totalTime.toFixed(0)}ms`);
96
-
97
- // ✅ Color-coded performance indicator
98
- if (totalTime < 100) {
99
- console.log(` ✅ Lightning fast! ⚡`);
100
- } else if (totalTime < 500) {
101
- console.log(` ✅ Fast rebuild`);
102
- } else if (totalTime < 1000) {
103
- console.log(` ⚠️ Moderate speed`);
104
- } else {
105
- console.log(` 🐌 Slow rebuild (consider optimizing)`);
65
+ // NEW: Verify Rebuild Artifacts
66
+ if (!verifyStaticBuild(distDir)) {
67
+ console.error('⚠️ Rebuild failed validation! Skipped client reload to preserve state.');
68
+ return false; // Return false prevents WebSocket reload message
106
69
  }
107
70
 
108
- console.log(`✅ Bundle rebuilt: ${mainJsFilename}\n`);
109
71
  return true;
110
72
 
111
73
  } catch (err) {
@@ -118,18 +80,10 @@ async function fullRebuild(juxSource, distDir) {
118
80
  /**
119
81
  * Start watching for file changes and rebuild on change
120
82
  */
121
- export function startWatcher(juxSource, distDir, wsClients) {
83
+ export function startWatcher(juxSource, distDir, wsClients, config, wsPort) {
122
84
  console.log(`👀 Watching: ${juxSource}`);
123
85
 
124
86
  const watcher = fs.watch(juxSource, { recursive: true }, async (eventType, filename) => {
125
- // Ignore non-.jux files and certain patterns
126
- if (!filename ||
127
- !filename.endsWith('.jux') ||
128
- filename.includes('node_modules') ||
129
- filename.includes('jux-dist') ||
130
- filename.startsWith('.')) {
131
- return;
132
- }
133
87
 
134
88
  // Debounce: If already rebuilding, queue another rebuild
135
89
  if (isRebuilding) {
@@ -140,8 +94,8 @@ export function startWatcher(juxSource, distDir, wsClients) {
140
94
  isRebuilding = true;
141
95
  console.log(`\n📝 File changed: ${filename}`);
142
96
 
143
- // Rebuild the entire bundle
144
- const success = await fullRebuild(juxSource, distDir);
97
+ // Rebuild the entire bundle (PASS PORT)
98
+ const success = await fullRebuild(juxSource, distDir, config, wsPort);
145
99
 
146
100
  isRebuilding = false;
147
101
 
@@ -168,4 +122,39 @@ export function startWatcher(juxSource, distDir, wsClients) {
168
122
  });
169
123
 
170
124
  return watcher;
125
+ }
126
+
127
+ async function rebuildOnChange(filePath) {
128
+ const rebuildStart = performance.now();
129
+ console.log(`\n📝 File changed: ${path.relative(projectRoot, filePath)}\n`);
130
+ console.log('🔄 Rebuilding bundle...');
131
+
132
+ try {
133
+ // ✅ FIX: Capture bundleResult from bundleJuxFilesToRouter
134
+ const bundleResult = await bundleJuxFilesToRouter(projectRoot, distDir, {
135
+ routePrefix: ''
136
+ });
137
+
138
+ // ✅ FIX: Pass bundleResult to generateIndexHtml
139
+ generateIndexHtml(distDir, bundleResult);
140
+
141
+ const rebuildTime = performance.now() - rebuildStart;
142
+
143
+ console.log(`\n⏱️ Rebuild Performance:`);
144
+ console.log(` Bundle generation: ${rebuildTime.toFixed(0)}ms`);
145
+ console.log(` Route/index.html: ${(performance.now() - rebuildStart - rebuildTime).toFixed(0)}ms`);
146
+ console.log(` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
147
+ console.log(` Total rebuild: ${(performance.now() - rebuildStart).toFixed(0)}ms`);
148
+
149
+ if (rebuildTime < 100) {
150
+ console.log(` ✅ Lightning fast! ⚡`);
151
+ }
152
+
153
+ // ✅ FIX: Notify clients with bundleResult
154
+ notifyClients({ type: 'reload', bundleResult });
155
+
156
+ } catch (error) {
157
+ console.error('❌ Rebuild failed:', error.message);
158
+ notifyClients({ type: 'error', message: error.message });
159
+ }
171
160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.0.62",
3
+ "version": "1.0.63",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "lib/jux.js",
@@ -49,16 +49,23 @@
49
49
  ],
50
50
  "scripts": {
51
51
  "build": "tsc",
52
+ "scaffold": "node lib/componentsv2/tools/Scaffold.js",
53
+ "create-skin": "node lib/componentsv2/tools/CreateSkin.js",
54
+ "doc-spam": "node lib/componentsv2/tools/DocSpam.js",
55
+ "test-compile-verifier": "node machinery/diagnose.js",
56
+ "check-types": "tsc --noEmit",
52
57
  "dev": "tsc --watch",
53
58
  "prepublishOnly": "npm run build",
54
59
  "analyze": "node scripts/analyze-components.js",
55
60
  "check-styles": "node scripts/check-inline-styles.js",
56
61
  "find-inject-styles": "node scripts/find-inject-styles.js",
57
- "find-jux-classes": "node scripts/find-jux-classes.js"
62
+ "find-jux-classes": "node scripts/find-jux-classes.js",
63
+ "test": "node --loader ts-node/esm tests/dropdown-test.js"
58
64
  },
59
65
  "dependencies": {
60
66
  "acorn": "^8.15.0",
61
67
  "axios": "^1.6.0",
68
+ "better-sqlite3": "^12.6.2",
62
69
  "chart.js": "^4.5.1",
63
70
  "chokidar": "^3.5.3",
64
71
  "esbuild": "^0.19.0",
@@ -68,10 +75,12 @@
68
75
  },
69
76
  "devDependencies": {
70
77
  "@types/express": "^4.17.17",
78
+ "@types/jsdom": "^27.0.0",
71
79
  "@types/node": "^20.0.0",
72
80
  "@types/ws": "^8.5.5",
73
81
  "acorn-walk": "^8.3.4",
74
82
  "jsdom": "^27.4.0",
83
+ "ts-node": "^10.9.2",
75
84
  "typescript": "^5.0.0"
76
85
  }
77
86
  }
@@ -1,200 +0,0 @@
1
- import { BaseComponent } from './base/BaseComponent.js';
2
- import { renderIcon } from './icons.js';
3
-
4
- // Event definitions
5
- const TRIGGER_EVENTS = [] as const;
6
- const CALLBACK_EVENTS = ['dismiss'] as const;
7
-
8
- export interface AlertOptions {
9
- message?: string;
10
- type?: 'info' | 'success' | 'warning' | 'error';
11
- dismissible?: boolean;
12
- icon?: string;
13
- style?: string;
14
- class?: string;
15
- }
16
-
17
- type AlertState = {
18
- message: string;
19
- type: string;
20
- dismissible: boolean;
21
- icon: string;
22
- visible: boolean;
23
- style: string;
24
- class: string;
25
- };
26
-
27
- export class Alert extends BaseComponent<AlertState> {
28
- private _alert: HTMLElement | null = null;
29
-
30
- constructor(id: string, options: AlertOptions = {}) {
31
- super(id, {
32
- message: options.message ?? '',
33
- type: options.type ?? 'info',
34
- dismissible: options.dismissible ?? true,
35
- icon: options.icon ?? '',
36
- visible: true,
37
- style: options.style ?? '',
38
- class: options.class ?? ''
39
- });
40
- }
41
-
42
- protected getTriggerEvents(): readonly string[] {
43
- return TRIGGER_EVENTS;
44
- }
45
-
46
- protected getCallbackEvents(): readonly string[] {
47
- return CALLBACK_EVENTS;
48
- }
49
-
50
- /* ═════════════════════════════════════════════════════════════════
51
- * FLUENT API
52
- * ═════════════════════════════════════════════════════════════════ */
53
-
54
- // ✅ Inherited from BaseComponent:
55
- // - style(), class()
56
- // - bind(), sync(), renderTo()
57
- // - addClass(), removeClass(), toggleClass()
58
- // - visible(), show(), hide(), toggleVisibility()
59
- // - attr(), attrs(), removeAttr()
60
- // - disabled(), enable(), disable()
61
- // - loading(), focus(), blur(), remove()
62
-
63
- message(value: string): this {
64
- this.state.message = value;
65
- return this;
66
- }
67
-
68
- type(value: 'info' | 'success' | 'warning' | 'error'): this {
69
- this.state.type = value;
70
- return this;
71
- }
72
-
73
- dismissible(value: boolean): this {
74
- this.state.dismissible = value;
75
- return this;
76
- }
77
-
78
- icon(value: string): this {
79
- this.state.icon = value;
80
- return this;
81
- }
82
-
83
- /* ═════════════════════════════════════════════════════════════════
84
- * RENDER
85
- * ═════════════════════════════════════════════════════════════════ */
86
-
87
- render(targetId?: string): this {
88
- const container = this._setupContainer(targetId);
89
-
90
- const { message, type, dismissible, icon, style, class: className } = this.state;
91
- const hasVisibleSync = this._syncBindings.some(b => b.property === 'visible');
92
-
93
- // Build alert element
94
- const alert = document.createElement('div');
95
- alert.className = `jux-alert jux-alert-${type}`;
96
- alert.id = this._id;
97
- if (className) alert.className += ` ${className}`;
98
- if (style) alert.setAttribute('style', style);
99
-
100
- if (icon) {
101
- const iconEl = document.createElement('span');
102
- iconEl.className = 'jux-alert-icon';
103
- iconEl.appendChild(renderIcon(icon));
104
- alert.appendChild(iconEl);
105
- }
106
-
107
- const content = document.createElement('div');
108
- content.className = 'jux-alert-content';
109
- content.textContent = message;
110
- alert.appendChild(content);
111
-
112
- if (dismissible) {
113
- const closeBtn = document.createElement('button');
114
- closeBtn.className = 'jux-alert-close';
115
- closeBtn.innerHTML = '×';
116
- alert.appendChild(closeBtn);
117
- }
118
-
119
- // Default dismiss behavior (only if NOT using sync)
120
- if (!hasVisibleSync && dismissible) {
121
- const closeBtn = alert.querySelector('.jux-alert-close');
122
- closeBtn?.addEventListener('click', () => {
123
- // 🎯 Fire the dismiss callback event
124
- this._triggerCallback('dismiss');
125
- alert.remove();
126
- });
127
- }
128
-
129
- // Wire events using inherited method
130
- this._wireStandardEvents(alert);
131
-
132
- // Wire sync bindings
133
- this._syncBindings.forEach(({ property, stateObj, toState, toComponent }) => {
134
- if (property === 'message') {
135
- const transform = toComponent || ((v: any) => String(v));
136
-
137
- stateObj.subscribe((val: any) => {
138
- const transformed = transform(val);
139
- content.textContent = transformed;
140
- this.state.message = transformed;
141
- });
142
- }
143
- else if (property === 'type') {
144
- const transform = toComponent || ((v: any) => String(v));
145
-
146
- stateObj.subscribe((val: any) => {
147
- const transformed = transform(val);
148
- alert.className = `jux-alert jux-alert-${transformed}`;
149
- if (className) alert.className += ` ${className}`;
150
- this.state.type = transformed;
151
- });
152
- }
153
- else if (property === 'visible') {
154
- const transformToState = toState || ((v: any) => Boolean(v));
155
- const transform = toComponent || ((v: any) => Boolean(v));
156
-
157
- let isUpdating = false;
158
-
159
- // State → Component
160
- stateObj.subscribe((val: any) => {
161
- if (isUpdating) return;
162
- const transformed = transform(val);
163
- alert.style.display = transformed ? 'flex' : 'none';
164
- });
165
-
166
- // Component → State (close button)
167
- if (dismissible) {
168
- const closeBtn = alert.querySelector('.jux-alert-close');
169
- closeBtn?.addEventListener('click', () => {
170
- if (isUpdating) return;
171
- isUpdating = true;
172
-
173
- alert.style.display = 'none';
174
- stateObj.set(transformToState(false));
175
-
176
- // 🎯 Fire the dismiss callback event
177
- this._triggerCallback('dismiss');
178
-
179
- setTimeout(() => { isUpdating = false; }, 0);
180
- });
181
- }
182
- }
183
- });
184
-
185
- container.appendChild(alert);
186
- this._alert = alert;
187
-
188
- requestAnimationFrame(() => {
189
- if ((window as any).lucide) {
190
- (window as any).lucide.createIcons();
191
- }
192
- });
193
-
194
- return this;
195
- }
196
- }
197
-
198
- export function alert(id: string, options: AlertOptions = {}): Alert {
199
- return new Alert(id, options);
200
- }