juxscript 1.1.279 → 1.1.281

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 (2) hide show
  1. package/machinery/autowrap.js +132 -16
  2. package/package.json +1 -1
@@ -69,20 +69,36 @@ function isAlreadyWrapped(stmt) {
69
69
  stmt.expression?.callee?.property?.name === '__watch';
70
70
  }
71
71
 
72
+ /**
73
+ * Extract the body statements from an existing __watch callback.
74
+ * Returns null if the statement is not a __watch or has no block body.
75
+ */
76
+ function getWatchBody(stmt) {
77
+ if (!isAlreadyWrapped(stmt)) return null;
78
+ const arg = stmt.expression.arguments?.[0];
79
+ if (!arg) return null;
80
+ if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') {
81
+ if (arg.body?.type === 'BlockStatement') {
82
+ return arg.body.body; // array of statements
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+
72
88
  function isSetupStatement(stmt) {
89
+ // Never treat statements referencing pageState as setup
90
+ if (containsPageStateRef(stmt)) return false;
91
+
73
92
  if (stmt.type === 'ImportDeclaration') return true;
74
93
  if (stmt.type === 'FunctionDeclaration') return true;
75
94
  if (stmt.type === 'ExpressionStatement') {
76
95
  const expr = stmt.expression;
77
96
  if (expr.type === 'CallExpression' &&
78
- expr.callee?.object?.name === 'jux' &&
79
- !containsPageStateRef(stmt)) return true;
97
+ expr.callee?.object?.name === 'jux') return true;
80
98
  if (expr.type === 'AwaitExpression' &&
81
- expr.argument?.callee?.object?.name === 'jux' &&
82
- !containsPageStateRef(stmt)) return true;
99
+ expr.argument?.callee?.object?.name === 'jux') return true;
83
100
  }
84
- if (stmt.type === 'VariableDeclaration' && !containsPageStateRef(stmt) && !containsJuxCall(stmt)) return true;
85
- if (stmt.type === 'VariableDeclaration' && containsJuxCall(stmt) && !containsPageStateRef(stmt)) return true;
101
+ if (stmt.type === 'VariableDeclaration') return true;
86
102
  return false;
87
103
  }
88
104
 
@@ -119,7 +135,30 @@ export function autowrap(source, filename = '') {
119
135
  const stmt = ast.body[i];
120
136
 
121
137
  if (isSetupStatement(stmt)) { i++; continue; }
122
- if (isAlreadyWrapped(stmt)) { i++; continue; }
138
+
139
+ // Check if this is a single __watch that contains multiple independent concerns
140
+ // If so, unwrap it and re-wrap each concern separately
141
+ if (isAlreadyWrapped(stmt)) {
142
+ const bodyStmts = getWatchBody(stmt);
143
+ if (bodyStmts && bodyStmts.length > 1) {
144
+ // Check if the body has multiple independent reactive groups
145
+ const groups = groupBodyStatements(bodyStmts, source);
146
+ if (groups.length > 1) {
147
+ // Replace single watch with multiple watches
148
+ // Record the original watch range for removal
149
+ const watchStart = getLineNumber(source, stmt.start);
150
+ const watchEnd = getLineNumber(source, stmt.end);
151
+ needsWatch.push({
152
+ line: watchStart,
153
+ endLine: watchEnd,
154
+ replace: true,
155
+ groups: groups,
156
+ });
157
+ }
158
+ }
159
+ i++;
160
+ continue;
161
+ }
123
162
 
124
163
  // VariableDeclaration reading pageState — group with subsequent stmts that use declared vars
125
164
  if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
@@ -172,17 +211,33 @@ export function autowrap(source, filename = '') {
172
211
  for (const item of sorted) {
173
212
  const startIdx = item.line - 1;
174
213
  const endIdx = item.endLine - 1;
175
- const blockLines = lines.slice(startIdx, endIdx + 1);
176
- const indent = blockLines[0].match(/^(\s*)/)[1];
177
214
 
178
- const wrapped = [
179
- `${indent}pageState.__watch(() => {`,
180
- ...blockLines.map(l => `${indent} ${l.trim()}`),
181
- `${indent}});`,
182
- ];
215
+ if (item.replace && item.groups) {
216
+ // Replace a single big __watch with multiple smaller ones
217
+ const indent = lines[startIdx].match(/^(\s*)/)[1];
218
+ const newLines = [];
219
+ for (const group of item.groups) {
220
+ newLines.push(`${indent}pageState.__watch(() => {`);
221
+ for (const gLine of group.lines) {
222
+ newLines.push(`${indent} ${gLine.trim()}`);
223
+ }
224
+ newLines.push(`${indent}});`);
225
+ }
226
+ lines.splice(startIdx, endIdx - startIdx + 1, ...newLines);
227
+ details.push(`L${item.line}-${item.endLine} (split ${item.groups.length})`);
228
+ } else {
229
+ const blockLines = lines.slice(startIdx, endIdx + 1);
230
+ const indent = blockLines[0].match(/^(\s*)/)[1];
231
+
232
+ const wrapped = [
233
+ `${indent}pageState.__watch(() => {`,
234
+ ...blockLines.map(l => `${indent} ${l.trim()}`),
235
+ `${indent}});`,
236
+ ];
183
237
 
184
- lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
185
- details.push(`L${item.line}-${item.endLine}`);
238
+ lines.splice(startIdx, endIdx - startIdx + 1, ...wrapped);
239
+ details.push(`L${item.line}-${item.endLine}`);
240
+ }
186
241
  }
187
242
 
188
243
  return {
@@ -191,3 +246,64 @@ export function autowrap(source, filename = '') {
191
246
  details
192
247
  };
193
248
  }
249
+
250
+ /**
251
+ * Groups statements inside a __watch body into independent reactive groups.
252
+ * Uses the same logic as the top-level grouping: VariableDeclarations that
253
+ * read pageState get grouped with following statements that use those vars.
254
+ * Standalone if-statements with pageState become their own group.
255
+ */
256
+ function groupBodyStatements(bodyStmts, source) {
257
+ const groups = [];
258
+ let i = 0;
259
+
260
+ while (i < bodyStmts.length) {
261
+ const stmt = bodyStmts[i];
262
+
263
+ // VariableDeclaration reading pageState — group with dependents
264
+ if (stmt.type === 'VariableDeclaration' && containsPageStateRef(stmt)) {
265
+ const varNames = getDeclaredVarNames(stmt);
266
+ const groupStmts = [stmt];
267
+ let j = i + 1;
268
+
269
+ while (j < bodyStmts.length) {
270
+ const next = bodyStmts[j];
271
+ if (varNames.some(v => usesIdentifier(next, v))) {
272
+ groupStmts.push(next);
273
+ j++;
274
+ } else {
275
+ break;
276
+ }
277
+ }
278
+
279
+ groups.push({
280
+ stmts: groupStmts,
281
+ lines: extractSourceLines(groupStmts, source),
282
+ });
283
+ i = j;
284
+ continue;
285
+ }
286
+
287
+ // Any statement referencing pageState — standalone group
288
+ if (containsPageStateRef(stmt)) {
289
+ groups.push({
290
+ stmts: [stmt],
291
+ lines: extractSourceLines([stmt], source),
292
+ });
293
+ i++;
294
+ continue;
295
+ }
296
+
297
+ // Non-reactive statement — attach to next reactive group or skip
298
+ i++;
299
+ }
300
+
301
+ return groups;
302
+ }
303
+
304
+ function extractSourceLines(stmts, source) {
305
+ if (stmts.length === 0) return [];
306
+ const start = stmts[0].start;
307
+ const end = stmts[stmts.length - 1].end;
308
+ return source.slice(start, end).split('\n');
309
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juxscript",
3
- "version": "1.1.279",
3
+ "version": "1.1.281",
4
4
  "type": "module",
5
5
  "description": "A JavaScript UX authorship platform",
6
6
  "main": "./dist/lib/index.js",