@lmjs/core 1.0.7 → 2.0.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.
package/src/_re.js ADDED
@@ -0,0 +1,2709 @@
1
+ // `cl` is normally aliased in vendor/reconnecting-websocket.js (`var cl =
2
+ // console.log;`), but that file is concatenated AFTER this one (see
3
+ // build/bundle.js) — and this file's own Proxy traps below call `cl(e)` in
4
+ // their catch handlers. That's reachable before the vendor file ever runs:
5
+ // index-bootstrap.js (also after this file, before the vendor one) can
6
+ // trigger _x()'s `set` trap (e.g. `_vt.Global.vars["x"] = ...` from a
7
+ // compiled index.js) before any view exists, which throws internally
8
+ // (`_vt.View._re` is undefined) and get caught right here — if `cl` isn't
9
+ // defined yet at that point, the catch handler itself throws, aborting
10
+ // whatever script called it (found 2026-09-14 testing against a real
11
+ // running dev server). Harmless if vendor/reconnecting-websocket.js's own
12
+ // `var cl = console.log;` runs later too — same value, redeclaration is a
13
+ // no-op.
14
+ var cl = console.log;
15
+
16
+ class _v {
17
+ static name;
18
+ static type;
19
+ static vars;
20
+ static fns;
21
+ static rvs;
22
+ static _pv;
23
+ static mx;
24
+ static views;
25
+ static hst;
26
+ static settings;
27
+ constructor(obj) {
28
+ this.name = obj.name ?? 'home';
29
+ this.type = obj.type ?? 'main';
30
+ this.hst = obj.hst ?? [];
31
+ this.views = obj.views ?? [];
32
+ this.vars = obj.vars ?? {};
33
+ // 2026-09-17: per-instance event-handler functions (see walk.js's
34
+ // matching addition, and lstnrs.js's evalEvAttr) — a subview's own
35
+ // `function bump(){}` still becomes a real `window.bump` too (plain
36
+ // <script>-tag semantics, unchanged), but multiple mounted
37
+ // instances of the same subview file all define a same-named
38
+ // function, so window.bump silently becomes whichever instance's
39
+ // script ran last — every instance's @click ended up calling THAT
40
+ // one instance's handler regardless of which was actually clicked.
41
+ // .fns is this instance's own, unambiguous copy.
42
+ this.fns = obj.fns ?? {};
43
+ this.rvs = obj.rvs ?? {};
44
+ this._pv = obj._pv ?? null;
45
+ this.mx = obj.mx ?? [];
46
+ this.settings = obj.settings ?? {
47
+ layout: "default",
48
+ requireAuth: false,
49
+ };
50
+ }
51
+ }
52
+
53
+ // Override console.log
54
+ const consoleLogOriginal = console.log;
55
+ console.log = function () {
56
+ for (let i = 0; i < arguments.length; i++) {
57
+ const arg = arguments[i];
58
+ if (arg && arg.hasOwnProperty('__isProxy') || arg?.target) {
59
+ arguments[i] = arguments[i].target;
60
+ }
61
+ }
62
+ consoleLogOriginal.apply(console, arguments);
63
+ };
64
+
65
+ // ---- Central error reporting (2026-09-13) ----------------------------------
66
+ // Replaces scattered `catch(e){cl(e)}` sites that silently swallowed errors.
67
+ // Wired into a representative, developer-facing subset for now: expression
68
+ // evaluation (scopedEval/lookup/evalExp), the pre-rewrite validator in
69
+ // walk.js, and a compiled <script> block's own runtime errors. Not yet wired
70
+ // into every catch site in this file — that's a deliberate, incremental scope,
71
+ // not an oversight; the ones above are where a developer's own .view mistake
72
+ // actually surfaces, which is what this phase set out to fix.
73
+ //
74
+ // _lumenDevMode is a plain constant for now, not wired to a real build/env
75
+ // flag yet — flip it by hand until that decision is made.
76
+ var _lumenDevMode = true;
77
+ var _lumenErrorLog = [];
78
+
79
+ // HST wire-format version this runtime expects (2026-09-15) — must be
80
+ // bumped in lockstep with packages/cli/lib/fcs.js's HST_FORMAT_VERSION
81
+ // whenever this file's walk()/renderHST() changes what fields it reads
82
+ // from the parser's output. See renderView()'s version check and fcs.js's
83
+ // own HST_FORMAT_VERSION comment for the full rationale.
84
+ var EXPECTED_HST_FORMAT_VERSION = 1;
85
+
86
+ // `_vt.View.vars["x"]` is what a runtime error will mention after the
87
+ // walk.js rewrite — this recovers the developer's original name `x` so the
88
+ // message reads the way the developer would expect, not the compiled form.
89
+ function _translateLumenError(message) {
90
+ if (!message) return message;
91
+ return message.replace(/_vt\.View\.vars\[["'`]([^"'`\]]+)["'`]\]/g, '$1');
92
+ }
93
+
94
+ function reportLumenError(info) {
95
+ info = info || {};
96
+ var rawMessage = info.message || (info.error && info.error.message) || 'Unknown error';
97
+ var entry = {
98
+ time: new Date().toISOString(),
99
+ stage: info.stage || 'runtime',
100
+ view: info.view || (typeof _vt !== 'undefined' && _vt.View ? _vt.View.name : undefined),
101
+ expr: info.expr,
102
+ message: _translateLumenError(rawMessage),
103
+ hint: info.hint
104
+ };
105
+ _lumenErrorLog.push(entry);
106
+
107
+ if (_lumenDevMode) {
108
+ console.error(
109
+ '[LumenJS] ' + entry.stage + ' error in "' + (entry.view || 'unknown') + '"' +
110
+ (entry.expr ? ' — ' + entry.expr : '') +
111
+ ': ' + entry.message +
112
+ (entry.hint ? '\n ' + entry.hint : '')
113
+ );
114
+ }
115
+ // Production stays quiet on-screen by design (point 6: don't crash the
116
+ // page the way V1's uncaught errors did) — the entry is still kept in
117
+ // _lumenErrorLog for later inspection; no telemetry hook wired yet.
118
+ return entry;
119
+ }
120
+
121
+
122
+ function _x(_x) {
123
+ var currPath = [];
124
+ // 2026-09-17: generalized from a position-specific check
125
+ // (`currPath[1] == "vars"`, only ever matched a flat `_vt.<root>.vars`
126
+ // write) to a real owner-walk — needed for per-instance subview
127
+ // scoping (`_vt.View.views[i].vars`, and its nested form
128
+ // `_vt.View.views[0].views[2].vars`), where "vars" can sit at any
129
+ // depth. `path` is currPath *without* the trailing "vars" segment.
130
+ // `Global` is a real special case, not a bug in this generalization:
131
+ // it has no `._re` of its own (see the object literal below — just
132
+ // `{vars:{}}`) since Global-var writes need to re-render whichever
133
+ // view is *currently* looking at that value, exactly like the
134
+ // pre-existing behavior this preserves. Everything else (View itself,
135
+ // or any views[i]/views[i].views[j]/... chain) is walked against the
136
+ // raw (non-proxied) tree — using the outer `_x` param directly, not
137
+ // the `_vt` proxy — so this traversal itself can't re-enter these
138
+ // traps and corrupt the in-flight currPath.
139
+ function _dispatchVarsUpdate(key) {
140
+ // "vars" can be anywhere in currPath, not just at index 1 — find
141
+ // it. The var name to update() is whatever comes right after it
142
+ // in currPath (e.g. `_vt.View.vars["obj"].nested = x` — the write
143
+ // itself is on "obj"'s own proxy, so `key` here is "nested", but
144
+ // the reactive var is "obj", one segment before it in currPath);
145
+ // if "vars" is the *last* segment, there's no next segment yet —
146
+ // that means this write IS the var (`_vt.View.vars["x"] = ...`),
147
+ // so `key` itself (the set() trap's own key) is the var name.
148
+ let varsIdx = currPath.indexOf("vars");
149
+ if (varsIdx === -1) return;
150
+ let varName = varsIdx < currPath.length - 1 ? currPath[varsIdx + 1] : key;
151
+ let rootPath = currPath.slice(0, varsIdx);
152
+ // Global has no ._re of its own (see its plain {vars:{}} literal
153
+ // below) — a Global write always re-renders whichever view is
154
+ // currently looking at it, exactly like the pre-existing behavior
155
+ // this preserves, for both the flat and nested-property case.
156
+ if (rootPath[0] === "Global") {
157
+ // 2026-09-18, real gap found and fixed: this file's own design
158
+ // comment above _vt's declaration says a Global var should be
159
+ // usable by "ordinary view code" just by referencing the
160
+ // name — but nothing ever made that true for a VIEW's own
161
+ // <script> statements (as opposed to mustaches/:if conditions,
162
+ // which already fall back to _vt.Global.vars via getVal()/
163
+ // evalExp()'s own merge). A view's compiled script only
164
+ // rewrites references to names IT ITSELF declares — it has no
165
+ // way to know index.js declared some other name as Global, so
166
+ // a bare reference to it (e.g. `rx.post(...)` where `rx =
167
+ // new RX(...)` was set in index.js's init()) resolved via the
168
+ // real JS scope chain, which only ever finds a real `window.rx`
169
+ // if something put it there — nothing did. Mirroring every
170
+ // Global write onto `window[varName]` too makes plain bare-
171
+ // identifier references resolve correctly and naturally,
172
+ // with zero per-script rewriting needed.
173
+ try {
174
+ if (typeof window !== 'undefined') window[varName] = _x.Global.vars[varName];
175
+ } catch (e) { }
176
+ if (_vt.View._re) _vt.View._re.update(varName);
177
+ return;
178
+ }
179
+ let owner = _x;
180
+ for (let i = 0; i < rootPath.length && owner; i++) {
181
+ owner = owner[rootPath[i]];
182
+ }
183
+ if (owner && owner._re) owner._re.update(varName);
184
+ }
185
+ const handler = {
186
+ get(target, key) {
187
+ if (key == "__isProxy") return true;
188
+ // 2026-09-14: reset must fire on ANY root key, not just "View" —
189
+ // this closure's currPath is shared by the whole _vt proxy tree,
190
+ // so a stale tail from a prior _vt.Global read (which never hit
191
+ // "View" to reset it) would otherwise get prepended to the next
192
+ // chain and corrupt which key set()'s update(key) fires for.
193
+ if (key == "View" || key == "Global") currPath = [];
194
+ currPath.push(key);
195
+ if (typeof target[key] === 'object' && target[key] !== null && key != "_re") {
196
+ // cl("salsaa", target, key)
197
+ return new Proxy(target[key], handler);
198
+ } else {
199
+ // cl("tartat", target, key);
200
+ return target[key] ?? (key == 'target' ? target : undefined) ?? undefined;
201
+ }
202
+ },
203
+ set(target, key, value) {
204
+
205
+ // cl(["hiiiiii", key, value]);
206
+ // console.log(`${key} set from ${target[key]} to ${value}`, target, Object.keys({ target })[0]);
207
+ target[key] = value;
208
+ // var _View = currPath[1] == "views" ? currPath[2] : "";
209
+
210
+ try {
211
+ // `_vt.View._re` genuinely doesn't exist yet during
212
+ // index.js's own bootstrap (before any view has mounted —
213
+ // see index-bootstrap.js) — that's the normal case now,
214
+ // not an error, so it's guarded (inside _dispatchVarsUpdate)
215
+ // rather than left to throw into the catch below.
216
+ _dispatchVarsUpdate(key);
217
+ } catch (e) {
218
+ cl(e);
219
+ }
220
+
221
+ currPath = [];
222
+ return true;
223
+ },
224
+ deleteProperty(target, key) {
225
+ if (!(key in target)) {
226
+ return false;
227
+ }
228
+ delete target[key];
229
+
230
+ try {
231
+ _dispatchVarsUpdate(key);
232
+ } catch (e) {
233
+ cl(e);
234
+ }
235
+ return true;
236
+ },
237
+ ownKeys(target) {
238
+ return Reflect.ownKeys(target);
239
+ },
240
+ has(target, key) {
241
+ return key in target;
242
+ },
243
+ defineProperty(target, key, descriptor) {
244
+ if (descriptor && "value" in descriptor) {
245
+ target[key] = descriptor.value;
246
+ }
247
+ return target;
248
+ },
249
+ getOwnPropertyDescriptor(target, key) {
250
+ const value = target[key];
251
+ return key in target
252
+ ? {
253
+ value,
254
+ enumerable: true,
255
+ configurable: true,
256
+ }
257
+ : undefined;
258
+ },
259
+ };
260
+ var x = new Proxy(_x, handler);
261
+ return x;
262
+ }
263
+
264
+ // "Global" (2026-09-14): a second, permanent root next to "View" — unlike
265
+ // _vt.View (replaced wholesale by a fresh _v() on every main-view navigation,
266
+ // see _lm's constructor below), this object is never reassigned, so state
267
+ // written here survives navigation. This is the target walk.js now rewrites
268
+ // index.js's top-level vars into, since index.js runs once at bootstrap and
269
+ // its vars (V1's documented convention for app-wide state like `isAdmin`)
270
+ // need to outlive any single view. concatVarsAtLevel() below merges it into
271
+ // every scope lookup so ordinary view code can just reference the name.
272
+ // 2026-09-17, real bug found and fixed: `.fns` was missing here (only
273
+ // `.vars` existed) — `walk.js`'s `_fnRegisterStatement()` (added for
274
+ // per-instance subview scoping, see the per-instance-scoping work above)
275
+ // unconditionally builds `<targetRoot>.fns["name"] = name;` for EVERY
276
+ // top-level function declaration, regardless of target — including
277
+ // `targetKey: "Global"`, used to compile index.js/project scripts. Any
278
+ // real project with so much as one function declaration in index.js (an
279
+ // extremely common pattern) crashed with "Cannot set properties of
280
+ // undefined (setting '<fnName>')" the moment that line ran. Nearly
281
+ // invisible in `lm serve` dev mode — index-bootstrap.js's synchronous XHR
282
+ // fetch-and-eval of the compiled index.js is wrapped in a try/catch that
283
+ // only `console.log`s the failure (never `console.error`/throws), so nothing
284
+ // looked broken as long as the crash landed after anything load-bearing
285
+ // (Reactor()) in that same script. Fatal in a real `lm build` production
286
+ // bundle, where the compiled index.js is concatenated inline, unguarded —
287
+ // an uncaught exception there aborts every remaining statement in the
288
+ // whole bundle file, breaking the entire app on every real production
289
+ // site with this pattern. Found deploying lumenjs.com's own real build.
290
+ let _vt = _x({
291
+ "View": new _v({}),
292
+ "Global": { "vars": {}, "fns": {} }
293
+ });
294
+
295
+ class _lm {
296
+ _RealDOM = [];
297
+ _effects = {};
298
+ _cc = {};
299
+ _jj = {};
300
+ _ready = false;
301
+ view = undefined;
302
+ sbscrbs = [];
303
+ reactiveVariables = [];
304
+ vrs = {};
305
+ _CXR = [];
306
+ _LXR = [];
307
+
308
+ constructor(view) {
309
+ this.view = view;
310
+ this.view._re = this;
311
+ this.reactiveVariables = view?.rvs ?? {};
312
+ this.init();
313
+ if (this.view.type == "main") _vt.View = this.view;
314
+
315
+ // this.reactiveVariables = this.view.vars ?? _vt.View.vars ?? [];
316
+
317
+ if (this.view._pv) {
318
+ // cl("Subscribing to " , this.view);
319
+ this.view._pv.subscribe(this.view);
320
+ }
321
+
322
+ return this;
323
+ }
324
+
325
+ init() {
326
+ var par = this;
327
+ this.view.hst.forEach(function (doc) {
328
+ par.walk(doc, null);
329
+ });
330
+ }
331
+
332
+ subscribe(view) {
333
+ this.sbscrbs.push(view);
334
+ }
335
+
336
+ scopedEval(context, expr, kk) {
337
+ // cl("Cleaning", expr, kk, context);
338
+ // 2026-09-14: `ctx` was read/deleted here before its own `let ctx =`
339
+ // declaration a few lines down — a real TDZ bug (ReferenceError:
340
+ // Cannot access 'ctx' before initialization), not something either
341
+ // of us introduced. Only triggers on the TypeError-retry path (a
342
+ // failed eval retrying with the offending property stripped), which
343
+ // is common enough — any expression touching a property of
344
+ // something momentarily undefined hits it. Fixed by computing `ctx`
345
+ // first, same effect, no TDZ violation.
346
+ let ctx = this.concatVarsAtLevel(context, this);
347
+ if (kk) {
348
+ if (!ctx.hasOwnProperty(kk)) return undefined;
349
+ delete ctx[kk];
350
+ }
351
+ // cl("CONTEXT");
352
+ // cl(context);
353
+ // cl(ctx);
354
+ try {
355
+ // cl(["LopIsMissing???", context, expr]);
356
+ const evaluator = Function.apply(null, [
357
+ ...Object.keys(ctx),
358
+ "expr",
359
+ "return eval(expr)",
360
+ ]);
361
+ // cl(evaluator.apply(null, [...Object.values(context), expr]));
362
+ // if(expr=="patientProfile['Gender']") alert([evaluator.apply(null, [...Object.values(context), expr]), expr, Object.keys(context), context['patientProfile'], Object.keys(context.patientProfile)]);
363
+ return evaluator.apply(null, [...Object.values(ctx), expr]);
364
+ } catch (e) {
365
+ // if (_debugMode)
366
+ // cl([expr, context, e]);
367
+ if (e instanceof TypeError) {
368
+ return this.scopedEval(ctx, expr, e.message.split(" ")[0]);
369
+ }
370
+ reportLumenError({ stage: 'expression', expr: expr, error: e });
371
+ return undefined;
372
+ }
373
+ }
374
+
375
+ getVals(effect) {
376
+ let val = '';
377
+ // let mss = [];
378
+ if (effect.type == "text") {
379
+ if (!effect.isSplit) {
380
+ for (let i = 0; i < effect.splits.length; i++) {
381
+ let split = effect.splits[i];
382
+ if (split.type == 'mustache') {
383
+ val += this.getVal(split.content);
384
+ } else {
385
+ val += split.content;
386
+ }
387
+ }
388
+ } else {
389
+ val = this.getVal(effect.content);
390
+ }
391
+ } else if (effect.type == "attr" || effect.type == "event") {
392
+ for (let i = 0; i < effect.splits.length; i++) {
393
+ let split = effect.splits[i];
394
+ if (split.type == 'mustache') {
395
+ val += this.getVal(split.content);
396
+ } else {
397
+ val += split.content;
398
+ }
399
+ }
400
+ // mss = effect.mss[effect.name];
401
+ }
402
+
403
+ // for (let i = 0; i < mss.length; i++) {
404
+ // const ms = "{{" + mss[i] + "}}";
405
+ // cl(ms, this.getVal(ms, _.cloneDeep(_vt.View.vars)));
406
+ // val = val.split(ms).join(this.getVal(ms, _.cloneDeep(this.reactiveVariables)));
407
+ // }
408
+
409
+ return val;
410
+ }
411
+
412
+ renderAll() {
413
+ if (this._ready) return; //CHECK APPLICABILITY
414
+ this._ready = true;
415
+
416
+
417
+ for (const rv in this._effects) {
418
+ if (Object.prototype.hasOwnProperty.call(this._effects, rv)) {
419
+ const effects = this._effects[rv];
420
+ for (let ei = 0; ei < effects.length; ei++) {
421
+ const effect = effects[ei];
422
+ // cl("Effect for " + rv, effect);
423
+ // effect.nd.textContent = "1234";
424
+
425
+ if (!effect.nd) continue;
426
+ // if (!effect.nd.isConnected) continue;
427
+ try {
428
+ if (effect.type == "text") {
429
+ // cl(k, effect);
430
+ effect.nd.textContent = this.render(effect);
431
+ } else if (effect.type == "attr") {
432
+ // cl("Setting Attr", k, effect, effect.name, this.render(effect));
433
+ if (effect.name == "value") {
434
+ effect.nd.value = this.render(effect);
435
+ } else {
436
+ effect.nd.setAttribute(effect.name, this.render(effect));
437
+ if (effect.name == "view") {
438
+ effect.nd.subPath = this.render(effect);
439
+ renderView(this.render(effect), true, {});
440
+ }
441
+ }
442
+ } else if (effect.type == "event") {
443
+ effect.nd.events[effect.name] = this.render(effect);
444
+ }
445
+ } catch (e) {
446
+ cl("Eeeeee", e);
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+
453
+ this.updateCXRs();
454
+ this.updateLXRs();
455
+ this.updateVXRs();
456
+
457
+ // 2026-09-18: same Reactor({tick}) fix as update() below — also
458
+ // fired on the very first render (not just subsequent reactive
459
+ // updates), since a real tick() use (e.g. hljs.highlightAll())
460
+ // needs to run on initial content too, not just after the first
461
+ // change.
462
+ if (typeof appSettings !== 'undefined' && appSettings && typeof appSettings.tick === 'function') {
463
+ try {
464
+ appSettings.tick();
465
+ } catch (e) {
466
+ cl(e);
467
+ }
468
+ }
469
+ }
470
+
471
+ chainConnected(cx) {
472
+ for (let i = cx.chain.length - 1; i >= 0; i--) {
473
+ const cxs = cx.chain[i];
474
+ if (cxs.ref.isPreConnected) {
475
+ return true;
476
+ }
477
+ }
478
+ return false;
479
+ }
480
+
481
+ async updateVXRs(k) {
482
+ let subsNames = [];
483
+ for (let i = 0; i < this.view.views.length; i++) {
484
+ const _view = this.view.views[i];
485
+ if (!subsNames.includes(_view.subPath)) subsNames.push(_view.subPath);
486
+ }
487
+ // cl(subsNames);
488
+ // 2026-09-17: real bug found and fixed — this used to always call
489
+ // the plain, no-args renderView(n, true, {}), which only ever
490
+ // searches _vt.View.views (the MAIN view's own array). That's
491
+ // correct when `this` IS the main view, but updateVXRs() also
492
+ // runs on every SUBVIEW instance's own _re (renderAll() calls it
493
+ // unconditionally) — for a subview that itself hosts further
494
+ // subviews, `this.view.views` holds THOSE, not the main view's,
495
+ // so the old call could never find them: nested subviews never
496
+ // rendered at all. Passing this.view.views + this.view.scopePath
497
+ // (set once, in renderHST(), when this instance itself was
498
+ // rendered — ["View"] for the main view, unchanged) lets
499
+ // renderView() search the RIGHT array and address the RIGHT
500
+ // nested scope regardless of how deep this instance is.
501
+ for (let i = 0; i < subsNames.length; i++) {
502
+ const n = subsNames[i];
503
+ renderView(n, true, {}, 'views', this.view.views, this.view.scopePath || ["View"]);
504
+ }
505
+ }
506
+
507
+ async updateCXRs(k) {
508
+ for (let i = 0; i < this._CXR.length; i++) {
509
+ const cx = this._CXR[i];
510
+ if (cx.name == "if") {
511
+ let isTrue = this.evalExp(cx.content, this.reactiveVariables);
512
+ // cl("IF", isTrue, cx.content, this.reactiveVariables, cx);
513
+ if (isTrue) {
514
+ await this.showSectionCX(cx, k);
515
+ } else {
516
+ await this.hideSectionCX(cx);
517
+ }
518
+ } else if (cx.name == "else-if") {
519
+ if (!this.chainConnected(cx)) {
520
+ let isTrue = this.evalExp(cx.content, this.reactiveVariables);
521
+ // cl("ELSE-IF", isTrue, cx.content, this.reactiveVariables, cx);
522
+ if (isTrue) {
523
+ await this.showSectionCX(cx, k);
524
+ } else {
525
+ await this.hideSectionCX(cx);
526
+ }
527
+ } else {
528
+ await this.hideSectionCX(cx);
529
+ }
530
+ } else if (cx.name == "else") {
531
+ // cl("ELSE", !this.chainConnected(cx), cx);
532
+ if (!this.chainConnected(cx)) {
533
+ await this.showSectionCX(cx, k);
534
+ } else {
535
+ await this.hideSectionCX(cx);
536
+ }
537
+ }
538
+
539
+ }
540
+ }
541
+
542
+ // @before-render / @after-render for :if/:else-if/:else (2026-09-14) —
543
+ // shared by all three updateCXRs() branches above. This runs on EVERY
544
+ // reactive update those branches are eligible for, not just when the
545
+ // condition actually flips — `cx.ref.node.isConnected` (a real, always-
546
+ // accurate DOM property) is what gates hook firing to genuine
547
+ // hidden->visible / visible->hidden transitions, not `isPreConnected`
548
+ // (an internal bookkeeping flag with more edge cases across the
549
+ // if/else-if/else chain than is worth relying on here).
550
+ async showSectionCX(cx, k) {
551
+ let wasConnected = cx.ref.node.isConnected;
552
+ cx.ref.isPreConnected = true;
553
+ await renderSection(cx.ref, cx.doc, this, k);
554
+ if (!wasConnected) await fireRenderHook(cx, 'after-render', cx.ref.node, { visible: true });
555
+ }
556
+
557
+ async hideSectionCX(cx) {
558
+ if (cx.ref.node.isConnected) await fireRenderHook(cx, 'before-render', cx.ref.node, { visible: false });
559
+ cx.ref.isPreConnected = false;
560
+ cx.ref.node.replaceWith(cx.ref);
561
+ }
562
+
563
+ render(effect) {
564
+ let x = "";
565
+
566
+ try {
567
+ if (effect.type == "text" || effect.type == "attr" || effect.type == "event") {
568
+ x = this.getVals(effect);
569
+ }
570
+ } catch (e) {
571
+ cl(e);
572
+ }
573
+
574
+ return x;
575
+ }
576
+
577
+ update(k) {
578
+ if (!this._ready) return;
579
+ // cl("Hello", k);
580
+
581
+
582
+ // cl(k, this._effects.hasOwnProperty(k));
583
+
584
+ if (this._effects.hasOwnProperty(k)) {
585
+ const effects = this._effects[k];
586
+ // cl(effects);
587
+ for (let ei = 0; ei < effects.length; ei++) {
588
+ const effect = effects[ei];
589
+ // cl("Effect for " + k, effect);
590
+ // effect.nd.textContent = "1234";
591
+
592
+ if (!effect.nd) continue;
593
+ // if (!effect.nd.isConnected) continue;
594
+ try {
595
+ if (effect.type == "text") {
596
+ // cl(k, effect);
597
+ effect.nd.textContent = this.render(effect);
598
+ } else if (effect.type == "attr") {
599
+ // cl("Setting Attr", k, effect, effect.name, this.render(effect));
600
+ if (effect.name == "value") {
601
+ effect.nd.value = this.render(effect);
602
+ } else {
603
+ effect.nd.setAttribute(effect.name, this.render(effect));
604
+ if (effect.name == "view") {
605
+ effect.nd.subPath = this.render(effect);
606
+ renderView(this.render(effect), true, {});
607
+ }
608
+ }
609
+ } else if (effect.type == "event") {
610
+ effect.nd.events[effect.name] = this.render(effect);
611
+ }
612
+ } catch (e) {
613
+ cl("Eeeeee", e);
614
+ }
615
+ }
616
+ }
617
+
618
+ this.updateCXRs(k);
619
+ this.updateLXRs(k);
620
+ for (let sbscsi = 0; sbscsi < this.sbscrbs.length; sbscsi++) {
621
+ const sbscr = this.sbscrbs[sbscsi];
622
+ sbscr._re.update(k);
623
+ }
624
+
625
+ // 2026-09-18: Reactor({tick}) — documented ("runs after every
626
+ // re-render, anywhere", lumenjs-spec.md §5.4/llms.md §3) but never
627
+ // actually called anywhere in this runtime — see this fix's fuller
628
+ // comment at Reactor()'s own definition (vendor/reconnecting-
629
+ // websocket.js) for how this was found. update() is the real
630
+ // per-reactive-change render point (unlike renderAll(), which only
631
+ // ever runs once per _lm instance, at initial mount) — this is
632
+ // where "after every re-render, anywhere" actually lives. Guarded
633
+ // on appSettings existing since update() can fire during index.js's
634
+ // own bootstrap, before Reactor() has run.
635
+ if (typeof appSettings !== 'undefined' && appSettings && typeof appSettings.tick === 'function') {
636
+ try {
637
+ appSettings.tick();
638
+ } catch (e) {
639
+ cl(e);
640
+ }
641
+ }
642
+ }
643
+
644
+ async updateLXRs(k) {
645
+ for (let i = 0; i < this._LXR.length; i++) {
646
+ var cx = this._LXR[i];
647
+
648
+ var forX = cx.forX;
649
+
650
+ if (k && k != forX['js']) continue;
651
+
652
+ var val = this.getVal(forX['js'], "");
653
+
654
+ // cl("FORR", cx, val);
655
+
656
+ var tempVal = [];
657
+ if (this.typeStr(val) == "number") {
658
+ for (let i = 0; i < val; i++) {
659
+ tempVal.push(i);
660
+ }
661
+ val = tempVal;
662
+ }
663
+
664
+
665
+ let vals = [];
666
+ let isObj = false;
667
+ if (this.typeStr(val) == "object") {
668
+ isObj = true;
669
+
670
+ for (const oKey in val) {
671
+ if (Object.hasOwnProperty.call(val, oKey)) {
672
+ const item = val[oKey];
673
+ let objj = {
674
+ key: oKey,
675
+ value: item,
676
+ };
677
+ vals.push(objj);
678
+ }
679
+ }
680
+ } else vals = clone(val);
681
+
682
+ if (this.typeStr(vals) == "array" && vals.length > 0) {
683
+ // cl("ARRAY", cx.ref, vals);
684
+ let forIf = cx.cond;
685
+ let limit = vals.length;
686
+ let offset = 0;
687
+ if (cx.limit) limit = (isNaN(cx.limit) ? cx.limit : limit) > vals.length ? vals.length : cx.limit * 1;
688
+ if (cx.offset) offset = (isNaN(cx.offset) ? cx.offset : offset) < 0 ? 0 : cx.offset * 1;
689
+
690
+
691
+ let marray = [];
692
+ if (forIf) {
693
+ marray = vals.slice(offset * 1, vals.length);
694
+ } else {
695
+ marray = vals.slice(offset * 1, limit * 1 + offset * 1);
696
+ }
697
+
698
+ // cl(cx, vals, limit, offset, forIf, marray);
699
+
700
+ let myLimit = 0;
701
+ // let myClones = [];
702
+
703
+ let arrayToRender = [];
704
+ let arrayToRenderVXs = [];
705
+
706
+ for (var index = 0; index < marray.length; index++) {
707
+ if (myLimit == limit * 1) break;
708
+ try {
709
+ let vx = {}; //clone(_vt.View.vars);
710
+ vx['index'] = myLimit;
711
+ if (forX['dx'] != "") vx[forX['dx']] = myLimit;
712
+ if (isObj) {
713
+ if (forX['as']['v'] != "") {
714
+ if (forX['as']['k']) vx[forX['as']['k']] = marray[index]['key'];
715
+ if (forX['as']['v']) vx[forX['as']['v']] = marray[index]['value'];
716
+ } else {
717
+ if (forX['as']['k']) vx[forX['as']['k']] = marray[index];
718
+ }
719
+ vx['key'] = marray[index]['key'];
720
+ vx['value'] = marray[index]['value'];
721
+ } else {
722
+ if (forX['as']['k']) vx[forX['as']['k']] = marray[index];
723
+ // 2026-09-17, real bug found and fixed: a bare
724
+ // `:for="items"` (no `as item` alias) is
725
+ // documented — and was V1's real, working
726
+ // behavior — to spread each item's own object
727
+ // keys directly into loop scope, so
728
+ // `items = [{number:1}]` lets a mustache read
729
+ // `{{number}}` with no alias at all. Nothing in
730
+ // this branch ever did that spread — `vx` had
731
+ // no alias key AND no spread keys, so a bare
732
+ // mustache like this always evaluated as an
733
+ // undefined identifier and silently rendered
734
+ // blank. Only spread for a plain-object item —
735
+ // a primitive (string/number) array item has no
736
+ // keys to spread and is read via the untouched
737
+ // alias path instead.
738
+ else if (marray[index] && typeof marray[index] === 'object') {
739
+ for (const k2 in marray[index]) {
740
+ if (Object.prototype.hasOwnProperty.call(marray[index], k2)) {
741
+ vx[k2] = marray[index][k2];
742
+ }
743
+ }
744
+ }
745
+ }
746
+ // cl(forX, vx);
747
+ // cl("VXXXX", vx, this);
748
+
749
+ if (forIf) {
750
+ // 2026-09-17, real bug found and fixed:
751
+ // evalExp()'s 2nd param is a NAMES array to
752
+ // look up in _vt.View.vars/_vt.Global.vars (see
753
+ // its other real call site, `this.evalExp(cx.content,
754
+ // this.reactiveVariables)`) — but `vx` here is
755
+ // a VALUES object (the just-built loop-item
756
+ // scope), not a names array. `vars.length` on a
757
+ // plain object is undefined, so evalExp's own
758
+ // `for (i=0; i<vars.length; i++)` loop never
759
+ // ran even once — every `:for-if` condition
760
+ // referencing the loop item (`item.value > 10`,
761
+ // the exact documented example) always
762
+ // evaluated against an empty scope and either
763
+ // threw (caught, filtered out) or read
764
+ // `undefined`. evalExp() already has a real,
765
+ // working mechanism for exactly this — it
766
+ // merges `this.vrs` (the loop-scope override
767
+ // object) at the highest priority — so route
768
+ // through that instead of a second, broken
769
+ // parameter shape.
770
+ let _prevVrs = this.vrs;
771
+ this.vrs = vx;
772
+ let isTrue;
773
+ try {
774
+ isTrue = this.evalExp(forIf, []);
775
+ } finally {
776
+ this.vrs = _prevVrs;
777
+ }
778
+ if (!isTrue) continue;
779
+ }
780
+
781
+ let miIndexx = offset * 1 + index * 1;
782
+
783
+
784
+ // let cln = this.createSection(cx, vx, forX, k);
785
+ arrayToRender.push(marray[index]);
786
+ arrayToRenderVXs.push(vx);
787
+ // let basket = await renderSection(cx.ref, cx.doc, this, k, {
788
+ // cx, vx, forX, miIndexx
789
+ // });
790
+ // cl(basket);
791
+ // cl([
792
+ // cx.ref.isConnected,
793
+ // cx.ref.node.isConnected,
794
+ // cx,
795
+ // "REAL INDEX: " + myLimit,
796
+ // miIndexx,
797
+ // forIf,
798
+ // isObj,
799
+ // forX,
800
+ // vx
801
+ // ]);
802
+ myLimit++;
803
+ } catch (e) {
804
+ cl(e);
805
+ }
806
+ }
807
+
808
+ // cl("ATR", arrayToRender);
809
+ let oldATR = cx.atr;
810
+ // 2026-09-17, real bug found and fixed: cx.atr used to
811
+ // store a reference to arrayToRender's own item objects,
812
+ // not an independent snapshot — arrayToRender's items are
813
+ // the actual current elements of the reactive array (e.g.
814
+ // _vt.View.vars["features"]), so a completely ordinary
815
+ // update pattern (mutate an item in place inside .map(),
816
+ // return the same reference — `f.done = !f.done; return
817
+ // f;`, not "rebuild a new object") left oldATR and the
818
+ // next render's arrayToRender pointing at the exact same,
819
+ // already-mutated objects by the time compareArrays()
820
+ // ran. deepCompare()'s JSON.stringify comparison was
821
+ // never wrong — it was comparing an object against
822
+ // itself, so every 'update' action was silently lost and
823
+ // the DOM never re-rendered. clone() (already used a few
824
+ // lines above for `vals`) gives cx.atr a true independent
825
+ // snapshot instead. Found clicking a real @click-toggled
826
+ // :for item on lumenjs.com's own real V2 homepage — no
827
+ // existing test used an in-place item mutation, only
828
+ // whole-array reassignment with new items (see render-
829
+ // hooks.integration.test.js's `images = [...,'c']`),
830
+ // which never exposed this since a length change always
831
+ // hits compareArrays()'s add/remove branches regardless.
832
+ cx.atr = clone(arrayToRender);
833
+ // cl(oldATR, arrayToRender);
834
+
835
+ const actions = this.compareArrays(oldATR, arrayToRender);
836
+ // cl("ACTNS", actions);
837
+
838
+ // @before-render / @after-render (2026-09-14) — lets a
839
+ // developer tear down and re-initialize a DOM-owning plugin
840
+ // (a slider, for instance) around this :for loop's own
841
+ // diff-and-patch pass, instead of it silently breaking the
842
+ // next time the underlying data changes. Skipped entirely
843
+ // when nothing actually changed. Fires on the very first
844
+ // render too (`actions` is all "add" then), so one
845
+ // @after-render handler covers both "initialize" and
846
+ // "re-initialize after data changed" — no separate manual
847
+ // bootstrap call needed. See fireRenderHook()'s own comment.
848
+ if (actions.length) await fireRenderHook(cx, 'before-render', cx.ref.parentElement, { items: arrayToRender, actions });
849
+
850
+ for (let ai = 0; ai < actions.length; ai++) {
851
+ const actn = actions[ai];
852
+ if (actn.action == 'add') {
853
+ //Add Items To The DOM at Index
854
+ // actn = {
855
+ // "action": "add",
856
+ // "index": 0,
857
+ // "element": {
858
+ // "key": "Name",
859
+ // "value": "Abdul Rahman"
860
+ // }
861
+ // };
862
+ let vx = arrayToRenderVXs[actn.index];
863
+ // cl([cx, vx, isObj, forX])
864
+ let cln = await this.createSection(cx, vx, isObj, forX);
865
+ cx.ref.before(cln);
866
+
867
+
868
+ cln.replaceWith(cln.node);
869
+ // cln.node.innerHTML = "";
870
+ // cln.node.append(...cln._re._RealDOM);
871
+ // cl(cln);
872
+
873
+ } else if (actn.action == 'remove') {
874
+
875
+ // cl("Remove", actn);
876
+ // let vx = arrayToRenderVXs[actn.index];
877
+ // let cln = await this.createSection(cx, vx);
878
+ // cx.ref.before(cln);
879
+
880
+ var keyed = cx.key + '_' + actn.index;
881
+
882
+ let tx = cx.ref?.nodes[keyed];
883
+
884
+ if (tx) {
885
+ tx.remove();
886
+ tx.node.remove();
887
+ delete cx.tx?.nodes[keyed];
888
+ // if(ref.isConnected){
889
+ // } else if(ref.node.isConnected){
890
+
891
+ // }
892
+ }
893
+
894
+ // cln.replaceWith(cln.node);
895
+ // cln.node.innerHTML = "";
896
+ // cln.node.append(...cln._re._RealDOM);
897
+ // cl(cln);
898
+ } else {
899
+ // let vx = arrayToRenderVXs[actn.index];
900
+ // let cln = await this.createSection(cx, vx);
901
+ // cx.ref.before(cln);
902
+
903
+ var keyed = cx.key + '_' + actn.index;
904
+
905
+ let tx = cx.ref?.nodes[keyed];
906
+
907
+ let vx = arrayToRenderVXs[actn.index];
908
+ if (tx) {
909
+ // tx._re.reactiveVariables = [...tx._re.reactiveVariables, ...Object.keys(vx)];
910
+ // cl("UPDATE", [tx], vx, tx._re);
911
+ tx._re.vrs = vx;
912
+ if (tx.isObj) {
913
+ if (tx.forX.as['k'] != "") tx._re.update(tx.forX.as['k']);
914
+ else tx._re.update(tx.forX.js);
915
+ if (tx.forX.as['v'] != "") tx._re.update(tx.forX.as['v']);
916
+ // cl("Updating", tx.forX.as['k'], tx.forX.as['v']);
917
+ for (let actnsi = 0; actnsi < actn.updates.length; actnsi++) {
918
+ const actnu = actn.updates[actnsi];
919
+ tx._re.update(actnu.property);
920
+ }
921
+ } else {
922
+ if (tx.forX.as['k'] != "") tx._re.update(tx.forX.as['k']);
923
+ else tx._re.update(tx.forX.js);
924
+ // cl("Updating", tx.forX);
925
+ }
926
+
927
+ // tx._re.renderAll();
928
+ // if(ref.isConnected){
929
+ // } else if(ref.node.isConnected){
930
+
931
+ // }
932
+ }
933
+
934
+ // cln.replaceWith(cln.node);
935
+
936
+ }
937
+ }
938
+
939
+ if (actions.length) await fireRenderHook(cx, 'after-render', cx.ref.parentElement, { items: arrayToRender, actions });
940
+
941
+ } else {
942
+ // cl("NO ARRAY", cx.ref);
943
+ cx.nodes = [];
944
+ }
945
+ // if (cx.name == "if") {
946
+ // let isTrue = this.evalExp(cx.value, this.reactiveVariables);
947
+ // if (isTrue) {
948
+ // await renderSection(cx.ref, cx.doc, this, k);
949
+ // } else {
950
+ // cx.ref.node.replaceWith(cx.ref);
951
+ // }
952
+ // }
953
+
954
+ }
955
+ }
956
+
957
+
958
+ compareLogic(array1, array2) {
959
+ if (array1.length === array2.length) {
960
+ // Perform update-only algorithm when arrays have the same length
961
+ // ...
962
+ return 1;
963
+ } else {
964
+ if (array1.length > array2.length) {
965
+ // Array1 has more elements, indicating there are deleted items
966
+ // Use two-way comparison to find deleted indexes
967
+ // If successful, perform the necessary removals
968
+ // Otherwise, proceed with update and removal comparisons
969
+ // ...
970
+ return 2;
971
+ } else {
972
+ // Array2 has more elements, indicating there are added items
973
+ // Use two-way comparison to find added indexes
974
+ // If successful, perform the necessary additions
975
+ // Otherwise, proceed with update and addition comparisons
976
+ // ...
977
+ return 3;
978
+ }
979
+ }
980
+ }
981
+
982
+ compareArrays(array1, array2) {
983
+ const actions = [];
984
+ // let cpl = this.compareLogic(array1, array2);
985
+
986
+ // if (cpl == 2) {
987
+ // cl("Deleted", this.findDeletedIndexes(array1, array2));
988
+ // } else if (cpl == 3) {
989
+ // cl("Added", this.findAddedIndexes(array1, array2));
990
+ // }
991
+
992
+ const maxLength = Math.max(array1.length, array2.length);
993
+ for (let i = 0; i < maxLength; i++) {
994
+ const element1 = array1[i];
995
+ const element2 = array2[i];
996
+
997
+ if (!element2) {
998
+ actions.push({
999
+ action: 'remove',
1000
+ index: i
1001
+ });
1002
+ } else if (!element1) {
1003
+ actions.push({
1004
+ action: 'add',
1005
+ index: i,
1006
+ element: element2
1007
+ });
1008
+ } else if (!this.deepCompare(element1, element2)) {
1009
+ actions.push({
1010
+ action: 'update',
1011
+ index: i,
1012
+ updates: this.getUpdates(element1, element2)
1013
+ });
1014
+ }
1015
+ }
1016
+
1017
+ return actions;
1018
+ }
1019
+
1020
+ findDeletedIndexes(array1, array2) {
1021
+ const deletedIndexes = [];
1022
+ let par = this;
1023
+
1024
+ array1.forEach((item, index) => {
1025
+ const foundIndex = array2.findIndex((el) => par.deepCompare(el, item));
1026
+ if (foundIndex === -1) {
1027
+ deletedIndexes.push(index);
1028
+ }
1029
+ });
1030
+
1031
+ return deletedIndexes;
1032
+ }
1033
+
1034
+ findAddedIndexes(array1, array2) {
1035
+ const addedIndexes = [];
1036
+ let par = this;
1037
+
1038
+ array2.forEach((item, index) => {
1039
+ const foundIndex = array1.findIndex((el) => par.deepCompare(el, item));
1040
+ if (foundIndex === -1) {
1041
+ addedIndexes.push(index);
1042
+ }
1043
+ });
1044
+
1045
+ return addedIndexes;
1046
+ }
1047
+
1048
+ deepCompare(obj1, obj2) {
1049
+ // Compare objects by stringifying and comparing their JSON representation
1050
+ return JSON.stringify(obj1) === JSON.stringify(obj2);
1051
+ }
1052
+
1053
+ getUpdates(oldObj, newObj) {
1054
+ const updates = [];
1055
+ for (const key in newObj) {
1056
+ if (newObj.hasOwnProperty(key) && newObj[key] !== oldObj[key]) {
1057
+ updates.push({ property: key, value: newObj[key] });
1058
+ }
1059
+ }
1060
+ return updates;
1061
+ }
1062
+
1063
+
1064
+ getVal(mo, indexName) {
1065
+ let vars = {};
1066
+ try {
1067
+ for (let i = 0; i < this.reactiveVariables.length; i++) {
1068
+ // 2026-09-14: fall back to _vt.Global.vars when a view
1069
+ // doesn't declare this var itself — the real-world case of
1070
+ // a mustache/:if referencing an index.js global (e.g.
1071
+ // `isAdmin`) that this view's own script never assigns.
1072
+ // concatVarsAtLevel() already does this merge for
1073
+ // lookup()/scopedEval(); getVal() (what mustache text nodes
1074
+ // actually call) had its own separate, unmerged read here
1075
+ // and was silently rendering these as empty.
1076
+ let __name = this.reactiveVariables[i];
1077
+ vars[__name] = _vt.View.vars.hasOwnProperty(__name)
1078
+ ? _vt.View.vars[__name]
1079
+ : _vt.Global.vars[__name];
1080
+ }
1081
+ for (const ky in this.vrs) {
1082
+ if (Object.prototype.hasOwnProperty.call(this.vrs, ky)) {
1083
+ vars[ky] = this.vrs[ky]
1084
+ }
1085
+ }
1086
+ // 2026-09-17: per-instance subview scoping — a subview's own
1087
+ // declared vars live on `this.view.vars` (wired up in
1088
+ // renderHST()/renderView() to be the SAME object its compiled
1089
+ // script's `_vt.View.views[i].vars[...]` writes reach), not
1090
+ // in the flat `_vt.View.vars` checked above (that's still
1091
+ // only ever the MAIN view). Checked last/highest-priority,
1092
+ // same shadowing shape as the `this.vrs` merge just above —
1093
+ // an instance's own var wins over anything it inherited.
1094
+ if (this.view && this.view.vars) {
1095
+ for (const ky in this.view.vars) {
1096
+ if (Object.prototype.hasOwnProperty.call(this.view.vars, ky)) {
1097
+ vars[ky] = this.view.vars[ky];
1098
+ }
1099
+ }
1100
+ }
1101
+ } catch (e) {
1102
+ cl(e);
1103
+ }
1104
+
1105
+ mo = (mo).trim();
1106
+ // cl(["getVal", mo, vars, indexName]);
1107
+ // if (mo == "patientProfile['Gender']") {
1108
+ // cl(["getVal", mo, vars, indexName]);
1109
+ // }
1110
+ if (mo.slice(0, 2) == "{{") {
1111
+ mo = mo.slice(2, -2);
1112
+ }
1113
+ // if (mo == 'new Date(article.createdAt.split("T")[0]).getDate()')
1114
+ // if(mo=="genders[`patientProfile['Gender']`]") cl(["getVal", mo, vars, indexName]);
1115
+ let value = "";
1116
+ let _mo = mo;
1117
+
1118
+ if (mo.indexOf("`") > -1) {
1119
+ var matchesVal = _mo.match(/\.`[\s\S]*?`/g);
1120
+ // cl(["shuuuu", matchesVal, _mo]);
1121
+ if (matchesVal)
1122
+ for (var y = 0; y < matchesVal.length; y++) {
1123
+ _mo = _mo
1124
+ .split(matchesVal[y])
1125
+ .join(
1126
+ "." +
1127
+ this.getVal(
1128
+ matchesVal[y].substr(1).slice(1, -1),
1129
+ indexName
1130
+ )
1131
+ );
1132
+ }
1133
+
1134
+ var matchesVal = _mo.match(/`[\s\S]*?`/g);
1135
+ // cl(["suuuu", matchesVal, _mo]);
1136
+ if (matchesVal)
1137
+ for (var y = 0; y < matchesVal.length; y++) {
1138
+ // cl([
1139
+ // "3uuu",
1140
+ // this.getVal(matchesVal[y].slice(1, -1), vars, indexName),
1141
+ // matchesVal,
1142
+ // y,
1143
+ // matchesVal[y].slice(1, -1),
1144
+ // vars,
1145
+ // indexName,
1146
+ // ]);
1147
+ _mo = _mo
1148
+ .split(matchesVal[y])
1149
+ .join(
1150
+ "'" +
1151
+ this.getVal(matchesVal[y].slice(1, -1), indexName) +
1152
+ "'"
1153
+ );
1154
+ }
1155
+
1156
+ // cl([this.getVal(_mo, vars, indexName), "kuuuuk", _mo, vars, indexName]);
1157
+ return this.getVal(_mo, indexName);
1158
+ }
1159
+
1160
+ if (mo.indexOf(";") > -1) {
1161
+ let zxx = mo.split(";");
1162
+ mo = $.trim(zxx[0]);
1163
+ }
1164
+
1165
+ if (mo.indexOf(" as ") > -1) {
1166
+ mo = mo.split(" as ");
1167
+ return this.getVal(mo[0], indexName);
1168
+ }
1169
+
1170
+ if (indexName) {
1171
+ indexName = indexName.toString();
1172
+ if (
1173
+ mo.indexOf(indexName) > -1 &&
1174
+ mo != indexName &&
1175
+ vars.hasOwnProperty(indexName) &&
1176
+ mo != "index"
1177
+ ) {
1178
+ mo = mo.split(indexName).join(vars[indexName]);
1179
+ return this.getVal(mo, indexName);
1180
+ }
1181
+ }
1182
+
1183
+ var Ondex = mo.match(/\bindex\b/g);
1184
+ if (Ondex && mo != "index" && vars.hasOwnProperty("index")) {
1185
+ _mo = mo.replace(/\bindex\b/g, vars["index"]);
1186
+ // cl(["Hassss",mo,vars["index"], vars, this.getVal(_mo, vars, indexName)]);
1187
+ return this.getVal(_mo, indexName);
1188
+ }
1189
+
1190
+ // if (this.isJS(mo, vars)) {
1191
+ // cl("get",mo);
1192
+ value = this.lookup(mo, vars);
1193
+ // } else {
1194
+ // value = this.lookup(mo, vars);
1195
+ // // if (mo == "index") cl("kiki", value);
1196
+ // }
1197
+
1198
+ // cl(["VALZZZZZZZZ", mo, vars, value, value ?? ""]);
1199
+ return value ?? "";
1200
+ }
1201
+
1202
+ concatVarsAtLevel(levelVars, parent) {
1203
+
1204
+ if (!parent.view._pv) {
1205
+ var concatenatedVars = { ...levelVars };
1206
+ if (parent.view.vars) {
1207
+ // Global vars (index.js's top-level state, see _vt.Global
1208
+ // above) sit at the lowest priority here — a view or a
1209
+ // narrower scope declaring the same name shadows it, same
1210
+ // as real JS scoping would.
1211
+ concatenatedVars = { ..._vt.Global.vars, ...parent.view.vars, ...concatenatedVars };
1212
+ }
1213
+ return concatenatedVars;
1214
+ }
1215
+
1216
+ var concatenatedVars = { ...levelVars };
1217
+ if (parent.view.vars) {
1218
+ // Object.assign(concatenatedVars, parent.view.vars);
1219
+ concatenatedVars = { ...parent.view.vars, ...concatenatedVars };
1220
+ }
1221
+
1222
+ return this.concatVarsAtLevel(concatenatedVars, parent.view._pv);
1223
+ }
1224
+
1225
+ lookup(name, vaz) {
1226
+
1227
+ // for (const key in _vt.View.vars) {
1228
+ // if (Object.prototype.hasOwnProperty.call(_vt.View.vars, key)) {
1229
+ // vaz[key] = _vt.View.vars[key];
1230
+ // }
1231
+ // }
1232
+ // if(name == '{{new Date(article.createdAt.split("T")[0]).getDate()}}')
1233
+ // cl(["lookup", name, vaz]);
1234
+ // cl(vaz);
1235
+ // cl(this.concatVarsAtLevel(vaz, this));
1236
+
1237
+ let vars = this.concatVarsAtLevel(vaz, this);
1238
+ // cl("VAAZZZ");
1239
+ // cl(vaz);
1240
+ // cl(vars);
1241
+
1242
+ try {
1243
+ var value;
1244
+ var names,
1245
+ index,
1246
+ lookupHit = false;
1247
+
1248
+ if (this.hasProperty(vars, name)) {
1249
+ value = vars[name];
1250
+ // cl(["luuuu", vars, name, value]);
1251
+ } else if (name.indexOf(".") > -1 && name.indexOf("[") == -1) {
1252
+ // cl(["laaaaaa", vars, name, value]);
1253
+ var value = this.scopedEval(vars, name);
1254
+ // cl(["liiii", vars, name, value]);
1255
+ if (!(value || value == 0)) {
1256
+ value = vars;
1257
+ names = name.split(".");
1258
+ index = 0;
1259
+ while (value != null && index < names.length) {
1260
+ if (index === names.length - 1)
1261
+ lookupHit = this.hasProperty(value, names[index]);
1262
+ value = value[names[index++]];
1263
+ //{{v.0.kuku}} v then v.0 then v.0.kuku ...... iterate through nested chains
1264
+ }
1265
+ }
1266
+ } else {
1267
+ var value = this.scopedEval(vars, name);
1268
+ // if (name == "patientProfile['Gender']") {
1269
+ // cl(["==zzzzzlookup", name, vaz, value, vars, name]);
1270
+ // }
1271
+ // if (name == 'new Date(article.createdAt.split("T")[0]).getDate()') cl(["laaa", vars, name, value]);
1272
+ if (!(value || value == 0)) {
1273
+ if (name.indexOf(".") == -1 && name.indexOf("[") > -1) {
1274
+ let _name = name;
1275
+ var matchesVal = _name.match(/\[[\s\S]*?\]/g);
1276
+ for (var y = 0; y < matchesVal.length; y++) {
1277
+ if (
1278
+ matchesVal[y].indexOf("'") == -1 &&
1279
+ matchesVal[y].indexOf('"') == -1
1280
+ )
1281
+ _name = _name
1282
+ .split(matchesVal[y])
1283
+ .join("['" + matchesVal[y].slice(1, -1) + "']");
1284
+ }
1285
+ var value = this.scopedEval(vars, _name);
1286
+ }
1287
+ }
1288
+ }
1289
+
1290
+ if (this.isFunction(value)) value = value.call(value);
1291
+
1292
+ // if (name == "patientProfile['Gender']") {
1293
+ // cl(["===========lookup", name, vaz, value]);
1294
+ // }
1295
+
1296
+ // if (name == 'new Date(article.createdAt.split("T")[0]).getDate()') cl(["lookup", name, vaz, value]);
1297
+
1298
+ // if (value || value==0) return value;
1299
+
1300
+ // else {
1301
+ // value = this.scopedEval(vars, name);
1302
+ // }
1303
+
1304
+ // cl("n: ",name," val: ",value," v: ",vaz)
1305
+ } catch (e) {
1306
+ reportLumenError({ stage: 'lookup', expr: name, error: e });
1307
+ return "";
1308
+ }
1309
+ // if (name == 'new Date(article.createdAt.split("T")[0]).getDate()') cl("halaaaaaa", value);
1310
+ // cl("halaaaaaa", value);
1311
+ return value;
1312
+ }
1313
+
1314
+
1315
+ objectToString = Object.prototype.toString;
1316
+ isArray =
1317
+ Array.isArray ||
1318
+ function isArrayPolyfill(object) {
1319
+ return objectToString.call(object) === "[object Array]";
1320
+ };
1321
+ isFunction(object) {
1322
+ return typeof object === "function";
1323
+ }
1324
+ typeStr(obj) {
1325
+ return this.isArray(obj) ? "array" : typeof obj;
1326
+ }
1327
+ hasProperty(obj, propName) {
1328
+ return obj != null && typeof obj === "object" && propName in obj;
1329
+ }
1330
+
1331
+ createEl(tag, attrs, children, events, doc) {
1332
+ const _el = document.createElement(tag);
1333
+
1334
+ // 2026-09-17: every element knows which _lm instance rendered it —
1335
+ // needed so a click on it can resolve its @click handler against
1336
+ // the RIGHT instance's own .fns (see lstnrs.js's evalEvAttr), and
1337
+ // (below) so an @init handler on a subview mount resolves against
1338
+ // ITS creator's own functions/scope, not window. Stamped before
1339
+ // anything below that might need it. Non-enumerable: real bug
1340
+ // found and fixed — an ordinary DOM element has no own enumerable
1341
+ // properties by default, so JSON.stringify(someElement) safely
1342
+ // produced "{}" everywhere this codebase already did that (e.g. a
1343
+ // real test asserting on JSON.stringify(view._slots), real DOM
1344
+ // nodes). An ENUMERABLE back-reference here closes a genuine
1345
+ // cycle (element -> _ownerRe -> that instance's own _RealDOM ->
1346
+ // the same element) and breaks every one of those call sites. Defined this way
1347
+ // instead of a plain `=` assignment so it's reachable
1348
+ // (`el._ownerRe`) but invisible to JSON.stringify/for-in/
1349
+ // Object.keys, matching how a parent-pointer is normally done.
1350
+ Object.defineProperty(_el, '_ownerRe', { value: this, enumerable: false, configurable: true, writable: true });
1351
+
1352
+ _el.isSub = false;
1353
+ if (attrs.hasOwnProperty('view')) {
1354
+ _el.isSub = true;
1355
+ _el.subPath = attrs['view'];
1356
+ // 2026-09-17: per-instance subview scoping. `views[]` used to
1357
+ // hold just the raw element — every instance of the same
1358
+ // subview file shared one flat _vt.View.vars, so two mounts of
1359
+ // the same .view file collided on the same variable slots (a
1360
+ // real, confirmed bug — clicking one instance changed a value
1361
+ // neither instance's DOM reflected). DOM elements already
1362
+ // carry arbitrary extra JS properties throughout this file
1363
+ // (.isSub, .subPath, .events) — .vars/.views here follow that
1364
+ // same convention rather than a new parallel structure. .vars
1365
+ // is this instance's own scope, addressed at
1366
+ // _vt.View.views[i].vars once renderHST() wires up the path
1367
+ // (see renderView()); .views lets this instance itself host
1368
+ // further nested subviews the same way.
1369
+ _el.vars = {};
1370
+ _el.views = [];
1371
+ _el.fns = {};
1372
+ // 2026-09-17: `@init="fn"` — the actual, decided mechanism
1373
+ // for giving a subview mount its own data at creation time
1374
+ // (replaces an earlier, narrower `:data="name"` attempt —
1375
+ // reusing the existing @before-render/@after-render hook
1376
+ // machinery instead of a bespoke attribute, per real
1377
+ // discussion of real use cases: a subview file mounted
1378
+ // multiple times needing distinct config per instance — the
1379
+ // same widget showing a different crypto coin per mount, or
1380
+ // a :for item needing its own data slice). `fn`'s return
1381
+ // value (a plain object) is merged into this instance's own
1382
+ // `.vars`, so it can hand over multiple named values at once
1383
+ // — not tied to matching a parent variable's name the way
1384
+ // `:data` was. Reads an ancestor's own variable by name
1385
+ // still needs no special syntax at all (getVal()/evalExp()'s
1386
+ // existing fallthrough already covers that); @init is for
1387
+ // data that's computed/selected specifically for this one
1388
+ // mount. fireRenderHook() needed a real fix first (used to
1389
+ // discard its handler's return value entirely) — see its own
1390
+ // comment. Deliberately one-way — a subview writing back to
1391
+ // a PARENT variable (a steps-form wizard sharing accumulated
1392
+ // state across steps, for instance) is a real, different,
1393
+ // not-yet-designed mechanism, not what this covers.
1394
+ //
1395
+ // Calls evalEvAttr() directly rather than fireRenderHook()
1396
+ // (which every other @before-render/@after-render call site
1397
+ // uses): createEl() itself isn't async, and this result has
1398
+ // to be ready synchronously, before the subview actually
1399
+ // renders (a later, separate, real async call). An @init
1400
+ // handler that returns a Promise can't be awaited here —
1401
+ // deliberately unsupported for now, not silently broken:
1402
+ // only a plain object return is used.
1403
+ if (doc && doc.evs && doc.evs.hasOwnProperty('@init')) {
1404
+ let _initAttr = doc.evs['@init'];
1405
+ if (_initAttr) {
1406
+ // 3rd arg: the CALLING scope's own loop-local bindings
1407
+ // (e.g. `item` from `:for="items as item"`) — these
1408
+ // aren't real JS variables an @init handler could
1409
+ // otherwise close over (it's an ordinary top-level
1410
+ // function, not defined inside the loop), so without
1411
+ // this there'd be no way for it to know which
1412
+ // iteration it's being called for at all.
1413
+ let _initResult = evalEvAttr(_initAttr, { cType: 'init' }, $(_el), 'init', this.vrs);
1414
+ if (_initResult && typeof _initResult === 'object' && typeof _initResult.then !== 'function') {
1415
+ Object.assign(_el.vars, _initResult);
1416
+ }
1417
+ }
1418
+ }
1419
+ this.view.views.push(_el);
1420
+ }
1421
+
1422
+ _el.events = {};
1423
+ for (const prop in attrs) {
1424
+ if (prop == "view" || prop == ":data" || prop == ":if" || prop == ":else-if" || prop == ":else" || prop == ":for" || prop == ":for-limit" || prop == ":for-offset" || prop == ":for-if") continue;
1425
+ try {
1426
+ // cl("DD", doc);
1427
+ let val = (doc && doc.ax.hasOwnProperty(prop)) ? "" : attrs[prop];
1428
+ if (prop == "value") {
1429
+ _el.value = val;
1430
+ } else _el.setAttribute(prop, val);
1431
+ } catch (e) {
1432
+ cl(e);
1433
+ }
1434
+ }
1435
+ for (const prop in events) {
1436
+ try {
1437
+ _el.events[prop] = events[prop];
1438
+ } catch (e) {
1439
+ cl(e);
1440
+ }
1441
+ }
1442
+ if (children.length) _el.append(...children);
1443
+
1444
+ // 2026-09-16: @after-render generalized to fire on MOUNT for every
1445
+ // element, not just :for items / :if sections. Those two already
1446
+ // get their own batch-level hook fired elsewhere (updateLXRs() /
1447
+ // showSectionCX() / hideSectionCX()) — with :items/:visible extra
1448
+ // data a per-element mount fire here can't provide, and firing
1449
+ // both would double-fire for them — so they're explicitly
1450
+ // excluded here via the same directive-attribute check createEl()
1451
+ // already does above. No @before-render equivalent for a generic
1452
+ // mount: there's no prior DOM state to act on before an element
1453
+ // exists for the first time (unlike :for/:if's before-render,
1454
+ // which runs against DOM that's already connected).
1455
+ if (events && events['@after-render'] && !(attrs && (attrs.hasOwnProperty(':for') || attrs.hasOwnProperty(':if') || attrs.hasOwnProperty(':else-if') || attrs.hasOwnProperty(':else')))) {
1456
+ fireRenderHook({ doc: doc }, 'after-render', _el, {});
1457
+ }
1458
+
1459
+ // 2026-09-16: V1's real automatic plugin-init pass, ported
1460
+ // faithfully from bea.js's own renderPlugins()/dtp() (real source:
1461
+ // SBEACDN/s.beacdn.com/beajs/core.js, not reverse-engineered from
1462
+ // minified code) — [sl]/[color]/[time]/[date]/[datetime] elements
1463
+ // become their respective widgets on mount, no explicit script
1464
+ // call needed, matching V1's documented behavior (lumenjs-
1465
+ // spec.md §5.6/§6.7). Built into core directly rather than living
1466
+ // only in the --with-plugins bundle (explicit choice, 2026-09-16)
1467
+ // — autoInitPlugins() itself guards every call on the matching
1468
+ // $.fn.* method actually existing, so a plain (no --with-plugins)
1469
+ // project's dom-shim-based bundle silently no-ops instead of
1470
+ // throwing when it encounters one of these attributes.
1471
+ autoInitPlugins(_el, attrs);
1472
+
1473
+ return _el;
1474
+ }
1475
+
1476
+ // processAttr(n, v, res) {
1477
+ // if (n == 'scoped') {
1478
+
1479
+ // } else if (n == 'binds') {
1480
+ // res.domNode.mxs = v.split(",").map((s) => s.trim());
1481
+ // } else {
1482
+ // if (n.substr(0, 1) == "@") {
1483
+ // res.domNode.setAttribute(n.substr(0,1), v);
1484
+ // } else res.domNode.setAttribute(n, v);
1485
+ // }
1486
+ // }
1487
+
1488
+ // getPVsScope(par) {
1489
+ // let _vz = {};
1490
+
1491
+ // if (par) {
1492
+ // cl("VZZZZ", par.view.vars);
1493
+ // _vz = this.getPVsScope(par.view._pv);
1494
+ // }
1495
+
1496
+ // return _vz;
1497
+ // }
1498
+
1499
+ // getScope() {
1500
+ // let vz = clone(_vt.View.vars) ?? {};
1501
+ // let _vz = this.getPVsScope(this.view._pv);
1502
+ // cl("VXXXX", vz, _vz);
1503
+
1504
+ // return vz;
1505
+ // }
1506
+
1507
+ evalExp(expr, vars) {
1508
+ // let svs = this.getScope();
1509
+ // cl("SVS", svs);
1510
+ // cl("SVS", expr, vars);
1511
+ let rvars = {};
1512
+ try {
1513
+ for (let i = 0; i < vars.length; i++) {
1514
+ // 2026-09-14: same Global-vars fallback as getVal() above —
1515
+ // without it this object always carries an explicit
1516
+ // `name: undefined` for anything index.js declares (since
1517
+ // it's never in _vt.View.vars), and because it's spread
1518
+ // LAST in concatVarsAtLevel()'s merge (highest priority),
1519
+ // that explicit undefined would silently clobber the real
1520
+ // value scopedEval() would otherwise have found in
1521
+ // _vt.Global.vars — breaking :if/:for conditions on any
1522
+ // index.js-declared var, not just mustache text (getVal()).
1523
+ let __name = vars[i];
1524
+ rvars[__name] = _vt.View.vars.hasOwnProperty(__name)
1525
+ ? _vt.View.vars[__name]
1526
+ : _vt.Global.vars[__name];
1527
+ }
1528
+ // 2026-09-17, real bug found and fixed: getVal() (mustache
1529
+ // text, e.g. {{f.name}}) has always merged this.vrs in after
1530
+ // the View/Global loop above — the local :for-loop-scoped
1531
+ // binding (f, k, v, index — set via tx._re.vrs = vx, see the
1532
+ // :for update path above) correctly shadows/supplies names a
1533
+ // view's own script never declares. evalExp() (used for
1534
+ // :if="f.done"/:else-if conditions) never had the same merge
1535
+ // — any :if/:else-if condition referencing a :for loop
1536
+ // variable's property could never evaluate correctly, in any
1537
+ // LumenJS V2 project, not just this page. It "looked" correct
1538
+ // on first render only by coincidence: :else isn't gated by
1539
+ // evalExp at all, so an all-false-by-default loop (every item
1540
+ // starting unchecked) rendered the same whether the condition
1541
+ // genuinely evaluated false or silently threw and was caught
1542
+ // below. Found clicking a real @click-toggled :for item on
1543
+ // lumenjs.com's own real V2 homepage.
1544
+ for (const ky in this.vrs) {
1545
+ if (Object.prototype.hasOwnProperty.call(this.vrs, ky)) {
1546
+ rvars[ky] = this.vrs[ky];
1547
+ }
1548
+ }
1549
+ // 2026-09-17: same per-instance subview merge as getVal()
1550
+ // above — a subview's own vars live on this.view.vars, not
1551
+ // the flat _vt.View.vars checked above (always the main
1552
+ // view). See getVal()'s matching comment for the full
1553
+ // rationale.
1554
+ if (this.view && this.view.vars) {
1555
+ for (const ky in this.view.vars) {
1556
+ if (Object.prototype.hasOwnProperty.call(this.view.vars, ky)) {
1557
+ rvars[ky] = this.view.vars[ky];
1558
+ }
1559
+ }
1560
+ }
1561
+ } catch (e) {
1562
+ cl(e);
1563
+ }
1564
+ //If Lookaup
1565
+ // let value = this.lookup(expr, vars);
1566
+ // if (value || value == 0) return false;
1567
+ try {
1568
+ var value = this.scopedEval(rvars, expr);
1569
+ // cl("VV", value);
1570
+ if (value && value != 0) return true;
1571
+ } catch (e) {
1572
+ reportLumenError({ stage: 'condition', expr: expr, error: e });
1573
+ return false;
1574
+ }
1575
+ return false;
1576
+ }
1577
+
1578
+ splitTextWithMustaches(text, mustaches) {
1579
+ // Sort mustaches by start index to process in order
1580
+ mustaches.sort((a, b) => a.start - b.start);
1581
+
1582
+ const elements = [];
1583
+ let currentIndex = 0;
1584
+
1585
+ for (const mustache of mustaches) {
1586
+ // Add static text before mustache if any
1587
+ if (currentIndex < mustache.start) {
1588
+ elements.push({
1589
+ type: 'static',
1590
+ content: text.substring(currentIndex, mustache.start)
1591
+ });
1592
+ }
1593
+
1594
+ // Add the mustache
1595
+ elements.push({
1596
+ type: 'mustache',
1597
+ jst: mustache.jst,
1598
+ rvs: mustache.rvs,
1599
+ content: text.substring(mustache.start, mustache.end)
1600
+ });
1601
+
1602
+ // Update current index
1603
+ currentIndex = mustache.end;
1604
+ }
1605
+
1606
+ // Add any remaining static text after the last mustache
1607
+ if (currentIndex < text.length) {
1608
+ elements.push({
1609
+ type: 'static',
1610
+ content: text.substring(currentIndex)
1611
+ });
1612
+ }
1613
+
1614
+ return elements;
1615
+ }
1616
+
1617
+
1618
+ walk(doc, parent) {
1619
+ var par = this;
1620
+ var tx, el;
1621
+ switch (doc.type) {
1622
+ case 'text':
1623
+
1624
+ if (doc.mss.length) {
1625
+
1626
+ let splitIt = true;
1627
+
1628
+ if (doc.tag == "textarea") {
1629
+ splitIt = false;
1630
+ }
1631
+
1632
+ if (splitIt) {
1633
+ let splits = this.splitTextWithMustaches(doc.content, doc.mss);
1634
+ // cl(splits);
1635
+ for (let si = 0; si < splits.length; si++) {
1636
+ const split = splits[si];
1637
+ if (split.type == 'static') {
1638
+ let txnd = document.createTextNode(split.content);
1639
+ if (!parent) par._RealDOM.push(txnd);
1640
+ (tx ?? (tx = [])).push(txnd);
1641
+ } else {
1642
+ let txnd = document.createTextNode("");
1643
+ for (let ri = 0; ri < split.rvs.length; ri++) {
1644
+ const element = split.rvs[ri];
1645
+ (this._effects[element] ?? (this._effects[element] = [])).push({
1646
+ "type": "text",
1647
+ "content": split.content,
1648
+ "jst": split.jst,
1649
+ "rvs": split.rvs,
1650
+ "isSplit": true,
1651
+ "nd": txnd,
1652
+ "tag": doc.tag
1653
+ });
1654
+ }
1655
+ if (!parent) par._RealDOM.push(txnd);
1656
+ (tx ?? (tx = [])).push(txnd);
1657
+ }
1658
+ }
1659
+ return tx;
1660
+ } else {
1661
+ tx = document.createTextNode("");
1662
+ for (let mui = 0; mui < doc.mss.length; mui++) {
1663
+ const mus = doc.mss[mui];
1664
+
1665
+ for (let ri = 0; ri < mus.rvs.length; ri++) {
1666
+ const element = mus.rvs[ri];
1667
+ (this._effects[element] ?? (this._effects[element] = [])).push({
1668
+ "type": "text",
1669
+ "content": doc.content,
1670
+ "splits": this.splitTextWithMustaches(doc.content, doc.mss),
1671
+ "jst": mus.jst,
1672
+ "rvs": mus.rvs,
1673
+ "isSplit": false,
1674
+ "nd": tx,
1675
+ "tag": doc.tag
1676
+ });
1677
+ }
1678
+ }
1679
+
1680
+ if (!parent) par._RealDOM.push(tx);
1681
+ return tx;
1682
+ }
1683
+
1684
+
1685
+ } else {
1686
+ tx = document.createTextNode(doc.content);
1687
+ if (!parent) par._RealDOM.push(tx);
1688
+ return tx;
1689
+ }
1690
+ break;
1691
+ case 'sections':
1692
+ case 'section':
1693
+ var _dd = md5(new Date().getTime() / 1000 + "::" + Math.random());
1694
+ tx = document.createTextNode("");
1695
+ var typeN = null;
1696
+ if (doc.attrs.hasOwnProperty(":else")) typeN = "else";
1697
+ else if (doc.attrs.hasOwnProperty(":else-if")) typeN = "else-if";
1698
+ else if (doc.attrs.hasOwnProperty(":if")) typeN = "if";
1699
+ else typeN = 'for';
1700
+
1701
+ if (doc.type == "section") {
1702
+
1703
+ var chain = [];
1704
+ if (typeN == "else-if" || typeN == "else") {
1705
+ try {
1706
+ let lastInChain = this._CXR.at(-1);
1707
+ if (lastInChain) {
1708
+ _dd = lastInChain.key;
1709
+ chain.push(...lastInChain.chain, lastInChain);
1710
+ }
1711
+ } catch (e) {
1712
+
1713
+ }
1714
+ }
1715
+
1716
+ el = par.createEl(doc.name, doc.attrs, [], doc.evs, doc);
1717
+ tx.node = el;
1718
+
1719
+ // cl("SECCCCC", doc);
1720
+ // return;
1721
+ this._CXR.push({
1722
+ "type": "section",
1723
+ "name": typeN,
1724
+ "content": doc.cond,
1725
+ "doc": doc,
1726
+ "key": _dd,
1727
+ "chain": chain,
1728
+ "ref": tx
1729
+ });
1730
+
1731
+ this.setEffects(doc, el);
1732
+
1733
+ } else if (doc.type == "sections") {
1734
+
1735
+ tx.key = _dd;
1736
+ tx.node = null;
1737
+ tx.nodes = {};
1738
+
1739
+ let docRaw = doc;
1740
+ // delete docRaw.attrs[':for'];
1741
+ // delete docRaw.attrs[':for-limit'];
1742
+ // delete docRaw.attrs[':for-if'];
1743
+ // delete docRaw.attrs[':for-offset'];
1744
+ // docRaw.type = 'tag';
1745
+
1746
+ // cl("SECCCCCSSS", doc);
1747
+ // return;
1748
+
1749
+ this._LXR.push({
1750
+ "type": "sections",
1751
+ "name": typeN,
1752
+ "cond": doc.attrs.hasOwnProperty(":for-if") ? doc.attrs[':for-if'] : null,
1753
+ "limit": doc.attrs.hasOwnProperty(":for-limit") ? doc.attrs[':for-limit'] : 0,
1754
+ "offset": doc.attrs.hasOwnProperty(":for-offset") ? doc.attrs[':for-offset'] : 0,
1755
+ "content": doc.content,
1756
+ "forX": doc.forX,
1757
+ "doc": docRaw,
1758
+ "key": _dd,
1759
+ "atr": [],
1760
+ "ref": tx
1761
+ });
1762
+
1763
+ // cl(tx, doc);
1764
+ }
1765
+
1766
+
1767
+ if (!parent) par._RealDOM.push(tx);
1768
+ return tx;
1769
+ break;
1770
+ case 'tag':
1771
+ var _dd = md5(new Date().getTime() / 1000 + "::" + Math.random());
1772
+
1773
+ // cl("DOC", doc);
1774
+
1775
+ if (doc.name.toLowerCase() == "settings") {
1776
+ if (par.view.type == "main") {
1777
+ var defaultSettings = {
1778
+ layout: "default",
1779
+ requireAuth: false,
1780
+ };
1781
+ try {
1782
+ let settingsC = doc.children[0].content;
1783
+ let settings = {};
1784
+ eval("settings = " + settingsC + ";");
1785
+ if (settings) {
1786
+ if (settings.layout == null) settings.layout = "default";
1787
+ if (settings.requireAuth == null) settings.requireAuth = false;
1788
+ par.view.settings = settings;
1789
+ } else {
1790
+ par.view.settings = defaultSettings;
1791
+ }
1792
+
1793
+ } catch (e) {
1794
+ setError(e, "Error in your settings tag inside the '" + par.view.name.toLowerCase() + "' main view!");
1795
+ par.view.settings = defaultSettings;
1796
+ }
1797
+ }
1798
+ } else if (doc.name.toLowerCase() == "script" || doc.name.toLowerCase() == "js") {
1799
+ let child = doc.children[0];
1800
+ let js = child.content;
1801
+ let jst = child.jst;
1802
+ let isScoped = false;
1803
+ if (doc.attrs.hasOwnProperty('scoped')) {
1804
+ delete doc.attrs['scoped'];
1805
+ isScoped = true;
1806
+ }
1807
+ el = par.createEl("script", doc.attrs, [], doc.evs, doc);
1808
+ el._sc = isScoped;
1809
+ el._dd = _dd;
1810
+ // el._js = js;
1811
+ el._jst = jst;
1812
+ // cl(child);
1813
+ // _jswacrk.trigger('msg', {
1814
+ // "code": js,
1815
+ // "type": par.view.type,
1816
+ // "key": _dd
1817
+ // });
1818
+ // _jsuid[_dd] = el;
1819
+ (par._jj[_dd] ?? (par._jj[_dd] = [])).push({
1820
+ "nd": el,
1821
+ });
1822
+
1823
+ } else if (doc.name.toLowerCase() == "style") {
1824
+ let css = doc.children[0].content;
1825
+ let isScoped = false;
1826
+
1827
+ el = par.createEl("style", {}, [], doc.evs, doc);
1828
+
1829
+ if (doc.attrs.hasOwnProperty('scoped')) {
1830
+ if (!parent) {
1831
+ if (par.view._dd) _dd = par.view._dd;
1832
+ else {
1833
+ par.view._dd = _dd;
1834
+ if (par.view.type == "main") $('[body]')[0].setAttribute('vuid', _dd);
1835
+ }
1836
+ } else {
1837
+ if (parent._dd) _dd = parent._dd;
1838
+ else {
1839
+ parent._dd = _dd;
1840
+ parent.setAttribute('vuid', _dd);
1841
+ }
1842
+ }
1843
+ delete doc.attrs['scoped'];
1844
+ isScoped = true;
1845
+ el._sc = isScoped;
1846
+ el._dd = _dd;
1847
+ el._css = css;
1848
+ (par._cc[_dd] ?? (par._cc[_dd] = [])).push({
1849
+ "nd": el,
1850
+ "_css": css
1851
+ });
1852
+ } else {
1853
+ el._sc = isScoped;
1854
+ el._dd = _dd;
1855
+ el.textContent = css;
1856
+ }
1857
+
1858
+
1859
+ for (const prop in doc.attrs) {
1860
+ try {
1861
+ _el.setAttribute(prop, doc.attrs[prop]);
1862
+ } catch (e) {
1863
+ cl(e);
1864
+ }
1865
+ }
1866
+ } else if (doc.name.toLowerCase() == "icon") {
1867
+ let child = par.createEl("span", {
1868
+ "class": "iconify",
1869
+ "data-icon": doc?.icon ?? "mdi:home"
1870
+ }, [], {}, null);
1871
+
1872
+ el = par.createEl("span", doc.attrs, [child], doc.evs, doc);
1873
+
1874
+ } else if (doc.name.toLowerCase() == "slot") {
1875
+ // Layout slot content (2026-09-15) — a view wraps
1876
+ // content meant for a named layout region (anything
1877
+ // other than the default `[body]` target) in
1878
+ // <slot name="...">...</slot>. Collected onto
1879
+ // par.view._slots[name] (an array of real, walked DOM
1880
+ // nodes) instead of being pushed into _RealDOM —
1881
+ // renderView() injects each populated slot into its
1882
+ // matching `[slot="name"]` layout element, after the
1883
+ // default content. Same non-participating-in-_RealDOM
1884
+ // shape as the `settings` branch above (no `el` gets
1885
+ // set here either) — but slot content needs to be
1886
+ // walked/rendered, unlike settings' raw config text, so
1887
+ // this uses a throwaway wrapper purely as a real DOM
1888
+ // parent for that walk; it's never attached anywhere.
1889
+ let slotName = doc.attrs && doc.attrs.name;
1890
+ if (slotName) {
1891
+ let tempWrapper = document.createElement('div');
1892
+ let childs = [];
1893
+ for (let i = 0; i < doc.children.length; i++) {
1894
+ const dc = doc.children[i];
1895
+ let chils = par.walk(dc, tempWrapper);
1896
+ if (chils) {
1897
+ if (Array.isArray(chils)) {
1898
+ if (chils.length) childs = [...childs, ...chils];
1899
+ } else {
1900
+ childs.push(chils);
1901
+ }
1902
+ }
1903
+ }
1904
+ (par.view._slots ?? (par.view._slots = {}))[slotName] = childs;
1905
+ }
1906
+ } else if (doc.attrs && doc.attrs.hasOwnProperty('tpl') && !doc.attrs.hasOwnProperty(':for')) {
1907
+ // 2026-09-17: `tpl="name"` used WITHOUT `:for=` on the
1908
+ // same element — the real, documented (lumenjs-
1909
+ // spec.md §4.3) shape always pairs the two, and only
1910
+ // makes sense paired: a template renders inside
1911
+ // whatever repeated context it's used from (see
1912
+ // renderSection()'s own tpl handling, for the
1913
+ // WITH-:for case — an element carrying both never
1914
+ // reaches this branch at all, since :for makes it a
1915
+ // 'sections'-type node the switch above already
1916
+ // dispatched elsewhere). Reported as a real error
1917
+ // rather than silently rendering nothing.
1918
+ reportLumenError({
1919
+ stage: 'tpl',
1920
+ error: new Error('tpl="' + doc.attrs['tpl'] + '" must be used together with :for on the same element — templates only render inside a repeated/list context.'),
1921
+ });
1922
+ } else {
1923
+ el = par.createEl(doc.name, doc.attrs, [], doc.evs, doc);
1924
+
1925
+ if (!doc.isV && !el.isSub) {
1926
+ let childs = [];
1927
+ for (let i = 0; i < doc.children.length; i++) {
1928
+ const dc = doc.children[i];
1929
+ let chils = par.walk(dc, el);
1930
+ if (chils) {
1931
+ if (Array.isArray(chils)) {
1932
+ if (chils.length) childs = [...childs, ...chils];
1933
+ } else {
1934
+ childs.push(chils);
1935
+ }
1936
+ }
1937
+ }
1938
+ childs.length ? el.append(...childs) : null;
1939
+ }
1940
+ }
1941
+
1942
+ this.setEffects(doc, el);
1943
+
1944
+ if (el) {
1945
+ if (!parent) par._RealDOM.push(el);
1946
+ }
1947
+ return el;
1948
+
1949
+ break;
1950
+ case 'comment':
1951
+
1952
+ break;
1953
+ default:
1954
+ break;
1955
+ }
1956
+ }
1957
+
1958
+ setEffects(doc, el) {
1959
+ let rvs = [];
1960
+ if (Object.keys(doc.ax).length) {
1961
+ for (const attrName in doc.ax) {
1962
+ if (Object.hasOwnProperty.call(doc.ax, attrName)) {
1963
+ const attrMustaches = doc.ax[attrName];
1964
+ for (let i = 0; i < attrMustaches.length; i++) {
1965
+ const mus = attrMustaches[i];
1966
+ for (let ri = 0; ri < mus.rvs.length; ri++) {
1967
+ const element = mus.rvs[ri];
1968
+ rvs.push(element);
1969
+ (this._effects[element] ?? (this._effects[element] = [])).push({
1970
+ "type": "attr",
1971
+ "name": attrName,
1972
+ "content": doc.attrs[attrName],
1973
+ "splits": this.splitTextWithMustaches(doc.attrs[attrName], doc.ax[attrName]),
1974
+ "jst": mus.jst,
1975
+ "rvs": mus.rvs,
1976
+ "nd": el
1977
+ });
1978
+ }
1979
+ }
1980
+ }
1981
+ }
1982
+ }
1983
+
1984
+ if (Object.keys(doc.ex).length) {
1985
+ for (const ky in doc.ex) {
1986
+ if (Object.hasOwnProperty.call(doc.ex, ky)) {
1987
+ const mss = doc.ex[ky];
1988
+ for (let i = 0; i < mss.length; i++) {
1989
+ const mus = mss[i];
1990
+ for (let ri = 0; ri < mus.rvs.length; ri++) {
1991
+ const element = mus.rvs[ri];
1992
+ rvs.push(element);
1993
+ (this._effects[element] ?? (this._effects[element] = [])).push({
1994
+ "type": "event",
1995
+ "name": ky,
1996
+ "content": doc.evs[ky],
1997
+ "splits": this.splitTextWithMustaches(doc.evs[ky], doc.ex[ky]),
1998
+ "jst": mus.jst,
1999
+ "rvs": mus.rvs,
2000
+ "nd": el
2001
+ });
2002
+ }
2003
+ }
2004
+ }
2005
+ }
2006
+ }
2007
+
2008
+ return rvs.filter((value, index, self) => {
2009
+ return self.indexOf(value) === index;
2010
+ });
2011
+ }
2012
+
2013
+ async createSection(cx, vx, isObj, forX) {
2014
+ var keyed = cx.key + '_' + vx.index;
2015
+ // cl("DD", cx, vx, keyed);
2016
+ var tx = document.createTextNode("");
2017
+ var typeN = null;
2018
+ if (cx.doc.attrs.hasOwnProperty(":else")) typeN = "else";
2019
+ else if (cx.doc.attrs.hasOwnProperty(":else-if")) typeN = "else-if";
2020
+ else if (cx.doc.attrs.hasOwnProperty(":if")) typeN = "if";
2021
+ else typeN = 'for';
2022
+
2023
+ // cl("CREATE", cx.doc.name, cx.doc.attrs, [], cx.doc.evs, cx.doc);
2024
+
2025
+ let el = this.createEl(cx.doc.name, cx.doc.attrs, [], cx.doc.evs, cx.doc);
2026
+ tx.node = el;
2027
+ tx.key = cx.key;
2028
+ tx.keyed = keyed;
2029
+ tx.isObj = isObj;
2030
+ tx.forX = forX;
2031
+ tx.vx = vx;
2032
+
2033
+ // cl("RENDERSECTION", tx, cx.doc, this, cx.key);
2034
+
2035
+ // this._CXR.push({
2036
+ // "type": "section",
2037
+ // "name": typeN,
2038
+ // "value": doc.cond,
2039
+ // "doc": doc,
2040
+ // "key": _dd,
2041
+ // "chain": chain,
2042
+ // "ref": tx
2043
+ // });
2044
+
2045
+
2046
+ let scsc = await renderSection(tx, cx.doc, this, cx.key);
2047
+ // cl(23);
2048
+ // cl(scsc);
2049
+ scsc._re = scsc;
2050
+
2051
+
2052
+
2053
+ // let rvs = scsc._re.setEffects(cx.doc, el);
2054
+ // // scsc._re.rvs = _vt.View.vars;
2055
+ // // scsc._re.reactiveVariables = rvs;
2056
+
2057
+ // scsc._re.renderAll('sections');
2058
+
2059
+ cx.ref.nodes[keyed] = tx;
2060
+
2061
+
2062
+ return tx;
2063
+ }
2064
+
2065
+
2066
+ attrString(attrs) {
2067
+ var buff = [];
2068
+ for (var key in attrs) {
2069
+ // if(key != "@click")
2070
+ buff.push(key + '="' + attrs[key] + '"');
2071
+ }
2072
+ if (!buff.length) return '';
2073
+ return ' ' + buff.join(' ');
2074
+ }
2075
+ _stringify(buff, doc) {
2076
+ var par = this;
2077
+ switch (doc.type) {
2078
+ case 'text':
2079
+ return buff + doc.content;
2080
+ case 'tag':
2081
+ buff += '<' + doc.name + (doc.attrs ? par.attrString(doc.attrs) : '') + (doc.isV ? '/>' : '>');
2082
+ if (doc.isV) return buff;
2083
+ for (let i = 0; i < doc.children.length; i++) {
2084
+ const dc = doc.children[i];
2085
+ buff = buff + (par._stringify('', dc));
2086
+ }
2087
+ return buff + '</' + doc.name + '>';
2088
+ case 'comment':
2089
+ // buff += '<!--' + doc.comment + '-->';
2090
+ return buff;
2091
+ default:
2092
+ return '';
2093
+ }
2094
+ }
2095
+ stringify(doc) {
2096
+ var par = this;
2097
+ return doc.reduce(function (token, rootEl) {
2098
+ return token + par._stringify('', rootEl);
2099
+ }, '');
2100
+ };
2101
+ }
2102
+
2103
+
2104
+ // 2026-09-17: `scopePath` (new) — where in the _vt tree THIS render's own
2105
+ // compiled <script> should write/read its reactive vars. Defaults to
2106
+ // ["View"] (the main view, unchanged behavior for every existing caller).
2107
+ // A subview mount passes its own ["View","views",i] (or the nested form
2108
+ // for a subview hosting a subview) — see renderView()'s isSub branch.
2109
+ // `ownVars`/`ownFns`/`ownViews` (new): for a subview render, the SAME
2110
+ // objects the mount element's `.vars`/`.fns`/`.views` already point to
2111
+ // (not fresh `{}`/`[]`s) — so a write through the compiled script's
2112
+ // `_vt.View.views[i].vars[...]`/`.fns[...]` path (which resolves to that
2113
+ // element's `.vars`/`.fns`) and a read through this instance's own
2114
+ // `getVal()`/`evalExp()` (`this.view.vars`) or `evalEvAttr()`
2115
+ // (`this.view.fns`) see the exact same data. `ownViews` matters for
2116
+ // NESTED subviews specifically: createEl() pushes onto `this.view.views`
2117
+ // while THIS instance is rendering — without linking that to the mount
2118
+ // element's own `.views`, a subview hosting further subviews pushed them
2119
+ // into a freshly-constructed, orphaned array nothing else could ever
2120
+ // find (real bug found testing exactly this — nested subviews mounted
2121
+ // and rendered their own content correctly, but their OWN vars/state
2122
+ // were unreachable from outside, landing in a throwaway array).
2123
+ async function renderHST(hst, n, type = 'main', tx, _pv = null, scopePath = ["View"], ownVars, ownFns, ownViews) {
2124
+
2125
+ var reactiveVariables = hst.reactiveVars;
2126
+ hst = hst.hst;
2127
+
2128
+ let _re = new _lm(new _v({
2129
+ "name": n,
2130
+ "type": type,
2131
+ "hst": hst,
2132
+ "vars": ownVars ?? tx?.vx,
2133
+ "fns": ownFns,
2134
+ "views": ownViews,
2135
+ "rvs": reactiveVariables,
2136
+ "_pv": _pv
2137
+ }));
2138
+ // 2026-09-17: persisted so THIS instance can later render ITS OWN
2139
+ // subviews (updateVXRs() -> renderView()) at the right nested path —
2140
+ // see both of those for the other half of this.
2141
+ _re.view.scopePath = scopePath;
2142
+
2143
+ if (Object.keys(_re._cc).length) {
2144
+
2145
+ // let cssObjs = {};
2146
+ // for (const ky in _re._cc) {
2147
+ // if (Object.hasOwnProperty.call(_re._cc, ky)) {
2148
+ // const csses = _re._cc[ky];
2149
+ // for (let inde = 0; inde < csses.length; inde++) {
2150
+ // const css = csses[inde];
2151
+ // let myKey = ky + "_::" + inde;
2152
+ // cssObjs[myKey] = css._css;
2153
+ // }
2154
+ // }
2155
+ // }
2156
+
2157
+
2158
+
2159
+
2160
+ for (const ky in _re._cc) {
2161
+ if (Object.hasOwnProperty.call(_re._cc, ky)) {
2162
+ const csses = _re._cc[ky];
2163
+ for (let inde = 0; inde < csses.length; inde++) {
2164
+ let prom = new defer();
2165
+ const css = csses[inde];
2166
+ _csswrk.trigger('css-ready', {
2167
+ "csses": [css._css],
2168
+ "pre": "[vuid='" + ky + "']",
2169
+ "key": ky
2170
+ });
2171
+ _vuid[ky] = prom;
2172
+ let _csses = await prom;
2173
+ css.nd.textContent = _csses[0];
2174
+
2175
+ // _jsuid[_dd] = el;
2176
+ // prom
2177
+ }
2178
+ }
2179
+ }
2180
+
2181
+
2182
+
2183
+ // let prom = new defer();
2184
+ // var _dd = md5(new Date().getTime() / 1000 + "::" + Math.random());
2185
+ // _csswrk.trigger('css-ready', {
2186
+ // "csses": cssObjs,
2187
+ // "key": _dd,
2188
+ // "pre": _dd
2189
+ // });
2190
+ // _vuid[_dd] = prom;
2191
+ // let _csses = await prom;
2192
+
2193
+ // cl(_csses);
2194
+
2195
+ // for (const ky in _re._cc) {
2196
+ // if (Object.hasOwnProperty.call(_re._cc, ky)) {
2197
+ // const csses = _re._cc[ky];
2198
+ // for (let inde = 0; inde < csses.length; inde++) {
2199
+ // const css = csses[inde];
2200
+ // let myKey = ky + "_::" + inde;
2201
+ // css.nd.textContent = _csses[myKey];
2202
+ // }
2203
+ // }
2204
+ // }
2205
+
2206
+ }
2207
+
2208
+ if (Object.keys(_re._jj).length) {
2209
+ for (const ky in _re._jj) {
2210
+ if (Object.hasOwnProperty.call(_re._jj, ky)) {
2211
+ const jses = _re._jj[ky];
2212
+ for (let inde = 0; inde < jses.length; inde++) {
2213
+ const nd = jses[inde].nd;
2214
+ let code = getWatcher(nd._jst, _re.view, reactiveVariables, scopePath).code;
2215
+ code = `try { ` + code + ` } catch (e) { reportLumenError({ stage: 'script', error: e }); }`;
2216
+ // sourceURL: DevTools shows this <script>'s errors against
2217
+ // "<viewname>.view" instead of an anonymous VM context.
2218
+ // 2026-09-14, real bug found testing a real project
2219
+ // end-to-end: this used to be appended BEFORE the
2220
+ // try/catch wrap above, landing the comment — and
2221
+ // everything after it on the same line, including the
2222
+ // wrap's own closing brace and catch clause — inside
2223
+ // the try block. A `//` comment eats the rest of its
2224
+ // line, so that silently commented out the catch
2225
+ // entirely and left an unterminated `try`, throwing
2226
+ // "Unexpected end of input" the moment any real
2227
+ // <script> block actually ran (about.view's real
2228
+ // script on roxyon.com, in this case). Must come after
2229
+ // the wrap, on its own trailing line.
2230
+ code = code + `\n//# sourceURL=` + (_re.view?.name || 'view') + `.view.generated.js`;
2231
+ nd.textContent = code;
2232
+ }
2233
+ }
2234
+ }
2235
+ }
2236
+
2237
+ return _re;
2238
+ }
2239
+
2240
+ // @before-render / @after-render (2026-09-14) — a developer-facing
2241
+ // lifecycle hook fired around a :for loop's diff-and-patch pass
2242
+ // (updateLXRs) or a :if/:else-if/:else section's show/hide transition
2243
+ // (updateCXRs' showSectionCX/hideSectionCX), letting a DOM-owning plugin
2244
+ // (a slider, for instance) safely tear down before its content is
2245
+ // replaced and re-initialize once it's settled — instead of silently
2246
+ // breaking the next time the underlying data or condition changes.
2247
+ // Reuses lstnrs.js's own evalEvAttr() — the same handler-resolution logic
2248
+ // every other @-attribute already goes through (@click, @sl-select, ...:
2249
+ // a plain function name calls window[name](ev, el, ...), anything else is
2250
+ // eval'd as an inline expression) — rather than inventing a second one;
2251
+ // the only new part is WHEN this gets called. Errors inside the handler
2252
+ // itself are already swallowed by evalEvAttr — matches the established,
2253
+ // silent-on-error precedent every other @-attribute handler already has;
2254
+ // not changed here to keep this hook's error behavior consistent with them.
2255
+ async function fireRenderHook(cx, n, containerEl, extra) {
2256
+ if (!containerEl) return;
2257
+ let attrKey = '@' + n;
2258
+ // The parser lifts @-prefixed attributes into `doc.evs` (its events
2259
+ // map) at compile time, same as @click/@sl-select/etc — NOT into
2260
+ // `doc.attrs` alongside plain attributes like :for/:if. Verified
2261
+ // against real parser output (fcs.js's getAllFiles), not assumed.
2262
+ if (!cx.doc || !cx.doc.evs || !cx.doc.evs.hasOwnProperty(attrKey)) return;
2263
+ let attrVal = cx.doc.evs[attrKey];
2264
+ if (!attrVal) return;
2265
+ let ev = Object.assign({ cType: n }, extra || {});
2266
+ let result = evalEvAttr(attrVal, ev, $(containerEl), n);
2267
+ if (result && typeof result.then === 'function') {
2268
+ // 2026-09-17: this used to just await-and-discard. @init (new,
2269
+ // subview mounts) needs the handler's actual return value — its
2270
+ // whole purpose is "compute this instance's own initial data" —
2271
+ // so a Promise result is now awaited AND returned, not thrown
2272
+ // away. Every existing @before-render/@after-render caller
2273
+ // already ignores this function's return value (none of them
2274
+ // needed it), so this is additive, not a behavior change for them.
2275
+ try { return await result; } catch (e) { return undefined; }
2276
+ }
2277
+ return result;
2278
+ }
2279
+
2280
+ // 2026-09-16: V1's real automatic plugin-init pass — [sl] (Select2),
2281
+ // [color] (bootstrap-colorpicker), [time]/[date]/[datetime]
2282
+ // (bootstrap-datetimepicker via dtp()) — ported faithfully from bea.js's
2283
+ // own real, un-minified renderPlugins()/dtp() source
2284
+ // (SBEACDN/s.beacdn.com/beajs/core.js:2846/3834, confirmed against the
2285
+ // real V1 build root this session already located, not reverse-engineered
2286
+ // from minified code). Called from createEl() on every element's mount.
2287
+ // Deliberately built into packages/core's own engine rather than kept
2288
+ // inside the --with-plugins bundle only — every call below is guarded on
2289
+ // the matching $.fn.* method actually existing, so a plain project (no
2290
+ // --with-plugins, dom-shim-based, none of these libraries loaded) safely
2291
+ // no-ops instead of throwing when a view happens to use one of these
2292
+ // attributes.
2293
+ function autoInitPlugins(el, attrs) {
2294
+ if (!attrs) return;
2295
+ try {
2296
+ if (attrs.hasOwnProperty('sl') && typeof $.fn.select2 === 'function') {
2297
+ initSl($(el));
2298
+ }
2299
+ if (attrs.hasOwnProperty('color') && typeof $.fn.colorpicker === 'function') {
2300
+ $(el).removeAttr('color').colorpicker({ format: 'rgba' });
2301
+ }
2302
+ if (typeof $.fn.datetimepicker === 'function') {
2303
+ if (attrs.hasOwnProperty('time')) dtp($(el), 'time');
2304
+ if (attrs.hasOwnProperty('date')) dtp($(el), 'date');
2305
+ if (attrs.hasOwnProperty('datetime')) dtp($(el), 'datetime');
2306
+ }
2307
+ } catch (e) { cl(e); }
2308
+ }
2309
+
2310
+ // Real V1 source, ported as-is (SBEACDN core.js:3404-3518) — the `sl-*`
2311
+ // attribute family is documented in lumenjs-spec.md §6.7. `:not(.select2-
2312
+ // hidden-accessible)`-equivalent guard kept even though this only ever
2313
+ // runs once per element (on mount, never re-checked) — matches V1's own
2314
+ // idempotency guard exactly, cheap insurance if this is ever called from
2315
+ // anywhere else in the future.
2316
+ function initSl(t) {
2317
+ if (t.hasClass('select2-hidden-accessible')) return;
2318
+ try {
2319
+ var plchldr = t.attr("placeholder") ? t.attr("placeholder") : "";
2320
+ var dir = $("body").hasClass("rtl") ? "rtl" : "ltr";
2321
+ var nr = t.attr("sl-nrmsg") ? t.attr("sl-nrmsg") : "No results found";
2322
+ var minResultsForSearch = t.attr("sl-mins") ? t.attr("sl-mins") : 10;
2323
+ var allowNewTags = t.attr("sl-ntgs") ? true : false;
2324
+ var dropdownParent = t.attr("sl-prt") ? t.attr("sl-prt") : "body";
2325
+ if (dropdownParent == "self") dropdownParent = t.parent();
2326
+ else dropdownParent = $(dropdownParent);
2327
+
2328
+ var query = t.attr("sl-query") ? t.attr("sl-query") : null;
2329
+ var uniquer = Date.now();
2330
+
2331
+ if (typeof window[query] === "function") {
2332
+ t.select2.amd.define(
2333
+ "adapt_" + uniquer,
2334
+ ["select2/data/array", "select2/utils"],
2335
+ function (ArrayAdapter, Utils) {
2336
+ function CustomDataAdapter($element, options) {
2337
+ CustomDataAdapter.__super__.constructor.call(this, $element, options);
2338
+ }
2339
+ Utils.Extend(CustomDataAdapter, ArrayAdapter);
2340
+ CustomDataAdapter.prototype.query = function (params, callback) {
2341
+ clearTimeout(_dbcrs[uniquer]);
2342
+ let _t = t;
2343
+ _dbcrs[uniquer] = setTimeout(function () {
2344
+ window[query](params, callback, _t);
2345
+ }, !_dbcrs.hasOwnProperty(uniquer) ? 0 : _dbcrsTime);
2346
+ };
2347
+ return CustomDataAdapter;
2348
+ }
2349
+ );
2350
+
2351
+ t.select2({
2352
+ dropdownParent: dropdownParent,
2353
+ minimumResultsForSearch: minResultsForSearch,
2354
+ placeholder: plchldr,
2355
+ dir: dir,
2356
+ tags: allowNewTags,
2357
+ allowClear: true,
2358
+ language: { noResults: function () { return nr; } },
2359
+ ...(t.select2.amd.require("adapt_" + uniquer)
2360
+ ? { ajax: {}, dataAdapter: t.select2.amd.require("adapt_" + uniquer) }
2361
+ : {}),
2362
+ });
2363
+ } else {
2364
+ t.select2({
2365
+ dropdownParent: dropdownParent,
2366
+ minimumResultsForSearch: minResultsForSearch,
2367
+ placeholder: plchldr,
2368
+ dir: dir,
2369
+ tags: allowNewTags,
2370
+ allowClear: true,
2371
+ language: { noResults: function () { return nr; } },
2372
+ });
2373
+ }
2374
+
2375
+ if (t.attr("sl-nosrch"))
2376
+ t.on("select2:opening select2:closing", function (event) {
2377
+ $(this).parent().find(".select2-search__field").prop("disabled", true);
2378
+ });
2379
+ if (t.attr("sl-class")) {
2380
+ t.on("select2:opening", function (event) { dropdownParent.addClass(t.attr("sl-class")); });
2381
+ t.on("select2:closing", function (event) { dropdownParent.removeClass(t.attr("sl-class")); });
2382
+ }
2383
+
2384
+ if (t.attr("sl-id") || t.attr("sl-text")) {
2385
+ let text = t.attr("sl-text");
2386
+ let id = t.attr("sl-id");
2387
+ if (!text) text = id;
2388
+ if (!id) id = text;
2389
+ let newOption = new Option(text, id, true, true);
2390
+ t.append(newOption).trigger('select');
2391
+ } else {
2392
+ t.select2("val", "");
2393
+ }
2394
+
2395
+ if (t.attr("sl-value")) t.val(t.attr("sl-value")).trigger('change');
2396
+ } catch (e) { cl(e); }
2397
+ }
2398
+ var _dbcrs = {};
2399
+ var _dbcrsTime = 250;
2400
+
2401
+ // Real V1 source, ported as-is (SBEACDN core.js:3834-3891) — bootstrap-
2402
+ // datetimepicker option builder + optional start/end date-linking. Not
2403
+ // documented in lumenjs-spec.md (see PROVENANCE.md's --with-plugins
2404
+ // section); this exact option shape was confirmed directly against V1's
2405
+ // real source once it was located this session, not reverse-engineered.
2406
+ function dtp(el, t) {
2407
+ el.removeAttr(t);
2408
+ let opts = {
2409
+ format: t == "date" ? "yyyy-mm-dd" : t == "time" ? "hh:ii" : "yyyy-mm-dd hh:ii",
2410
+ weekStart: el.attr("date-week-start") ?? 1,
2411
+ startView: t == "time" ? 1 : (el.attr("startview") ? el.attr("startview") : 2),
2412
+ minView: el.attr("minview") ? el.attr("minview") : t == "time" ? 0 : t == "datetime" ? 0 : 2,
2413
+ maxView: el.attr("maxview") ? el.attr("maxview") : t == "time" ? 1 : 4,
2414
+ todayBtn: t == "time" ? 0 : el.attr("date-today") == "false" ? 0 : 1,
2415
+ todayHighlight: t == "time" ? 0 : el.attr("date-today") == "false" ? 0 : 1,
2416
+ language: el.attr("date-lang") ?? "en",
2417
+ minuteStep: el.attr("date-minute-step") ?? 5,
2418
+ pickerPosition: el.attr("date-position") ?? "top-right",
2419
+ autoclose: 1,
2420
+ showMeridian: false,
2421
+ };
2422
+ if (el.attr("date-start")) opts["startDate"] = el.attr("date-start");
2423
+ if (el.attr("date-end")) opts["endDate"] = el.attr("date-end");
2424
+ if (el.attr("date-value")) opts["date"] = el.attr("date-value");
2425
+
2426
+ el.datetimepicker(opts);
2427
+
2428
+ if (t == "time") {
2429
+ el.on("show", function (ev) {
2430
+ $(".table-condensed > thead, .table-condensed > thead > tr > th, .table-condensed > thead > tr > th > i")
2431
+ .attr("style", "visibility: hidden; font-size:0px !important; overflow: hidden; height: 0px;");
2432
+ }).on("hide", function (ev) {
2433
+ $(".table-condensed > thead, .table-condensed > thead > tr > th, .table-condensed > thead > tr > th > i")
2434
+ .attr("style", "visibility: visible;");
2435
+ });
2436
+ }
2437
+ if (el.attr("date-link-start")) {
2438
+ el.on("change", function (e) {
2439
+ let dp1 = el.data("datetimepicker");
2440
+ let dp2 = $(el.attr("date-link-start")).data("datetimepicker");
2441
+ dp2.setStartDate(dp1.getFormattedDate());
2442
+ if (dp2.getFormattedDate() < dp1.getFormattedDate() || dp2.getFormattedDate() == "")
2443
+ $(el.attr("date-link-start")).val(dp1.getFormattedDate());
2444
+ });
2445
+ } else if (el.attr("date-link-end")) {
2446
+ el.on("change", function (e) {
2447
+ let dp1 = $(el.attr("date-link-end")).data("datetimepicker");
2448
+ let dp2 = el.data("datetimepicker");
2449
+ dp1.setEndDate(dp2.getFormattedDate());
2450
+ });
2451
+ opts["useCurrent"] = false;
2452
+ }
2453
+ }
2454
+
2455
+ async function renderSection(tx, doc, par, k, sectionsData) {
2456
+ if (tx.node.isConnected) {
2457
+ if (k) {
2458
+ tx._re.update(k);
2459
+ }
2460
+ return tx._re;
2461
+ }
2462
+
2463
+
2464
+ var hst = doc.children;
2465
+
2466
+ // 2026-09-17: `tpl="name"` on a :for element (`<li :for="items as
2467
+ // item" tpl="row"></li>`, the real, documented — lumenjs-spec.md
2468
+ // §4.3 — shape; never built in V2 until now). A :for element's own
2469
+ // `doc.children` is empty (there's nothing between its tags in the
2470
+ // source), which is exactly what's rendered per item above — so
2471
+ // this substitutes the template file's own HST as this section's
2472
+ // children instead, walked by the SAME engine instance rendering
2473
+ // everything else here (this section already has its own vx/vrs —
2474
+ // no separate scope, no isolated instance, just this item's content).
2475
+ if (doc.attrs && doc.attrs.hasOwnProperty('tpl')) {
2476
+ let _payload = (typeof _vcD !== 'undefined' && _vcD) || (typeof _vcData !== 'undefined' ? _vcData : undefined);
2477
+ let _tplName = doc.attrs['tpl'];
2478
+ let _tplKey = btoa("src/tpls/" + _tplName + ".tpl");
2479
+ let _tplEntry = _payload && _payload.tpls && _payload.tpls[_tplKey];
2480
+ if (_tplEntry) {
2481
+ hst = _tplEntry.hst;
2482
+ } else {
2483
+ reportLumenError({
2484
+ stage: 'tpl',
2485
+ error: new Error('tpl="' + _tplName + '" — no such file at src/tpls/' + _tplName + '.tpl'),
2486
+ });
2487
+ }
2488
+ }
2489
+
2490
+ // 2026-09-17: real bug found and fixed — a subview mounted INSIDE a
2491
+ // :for item (e.g. <div :for="items as item"><div view="widgets/
2492
+ // card"></div></div>) never worked at all: renderHST() used to always
2493
+ // build a fresh, empty `.views` array for whatever it constructs
2494
+ // (see _v's constructor), so createEl() calls made while rendering
2495
+ // THIS item's own content pushed the subview's mount element onto a
2496
+ // throwaway array nothing else could ever find — renderView()'s
2497
+ // isSub search only ever looks at the ENCLOSING view's real `.views`
2498
+ // (or a nested subview's own, itself wired the same way). Passing
2499
+ // par's own `.view.views`/`.scopePath` through here makes every
2500
+ // loop item share the SAME real registry as its enclosing view, so
2501
+ // subview mounts inside a loop accumulate into one addressable array
2502
+ // (sequential index, in render order) exactly like a static mount —
2503
+ // same mechanism either way, just a dynamically-arriving index.
2504
+ let _re = await renderHST({ hst, mxes: [] }, tx.key, 'section', tx, par, par?.view?.scopePath || ["View"], undefined, undefined, par?.view?.views);
2505
+ // if(par) {
2506
+ // _re.reactiveVariables = par.reactiveVariables;
2507
+ // }
2508
+ tx._re = _re;
2509
+
2510
+ // if (doc.type == "sections") {
2511
+
2512
+ // return _re;
2513
+ // }
2514
+ // cl(tx.key);
2515
+ // cl(par);
2516
+ // cl(hst);
2517
+ // cl(tx);
2518
+ // cl(doc);
2519
+ // cl(_re);
2520
+
2521
+ // cl("section", _re, par);
2522
+
2523
+ if (tx.node) {
2524
+ tx.replaceWith(tx.node);
2525
+ tx.node.innerHTML = "";
2526
+ tx.node.append(..._re._RealDOM);
2527
+
2528
+ _re.setEffects(doc, tx.node);
2529
+
2530
+ _re.renderAll('section');
2531
+ }
2532
+ return _re;
2533
+ }
2534
+
2535
+ // `viewsArr`/`scopeBase` (2026-09-17): which subview registry to search
2536
+ // and which scope path to address matches under — default to the main
2537
+ // view's own (_vt.View.views / ["View"]) for the top-level, non-recursive
2538
+ // caller (ws.js's real navigation, index-bootstrap.js, etc. — all
2539
+ // unaffected, still work exactly as before). A subview instance rendering
2540
+ // ITS OWN subviews (updateVXRs(), above) passes its own `.views`/
2541
+ // `.scopePath` instead, so nested subviews resolve against the RIGHT
2542
+ // array at the RIGHT nested address instead of always the main view's.
2543
+ async function renderView(n, isSub, d, type = 'views', viewsArr, scopeBase = ["View"]) {
2544
+ let filePath = "src/views/" + n + ".view"
2545
+ if (type == 'layouts') filePath = "src/layouts/" + n + ".layout";
2546
+
2547
+ cl(arguments);
2548
+ let fileKey = btoa(filePath);
2549
+ if (!isSub) {
2550
+ View.props = d ?? {};
2551
+ var _queryParams = window.location.href.split("?");
2552
+ var nn = _queryParams.shift();
2553
+ View.params = paraToObj(_queryParams) ?? {};
2554
+ }
2555
+ n = prepareNode(n);
2556
+ var _queryParams = n.split("?");
2557
+ n = _queryParams.shift();
2558
+
2559
+ // 2026-09-15, real bug found implementing HST versioning: `_vcD` is
2560
+ // only ever assigned by ws.js's syncViewFiles() (dev-only, gated on
2561
+ // `!_beaTn`) — production never runs that, so `_vcD` itself stays
2562
+ // undefined forever in a real prod build, even though the SAME data
2563
+ // (views/layouts) is already sitting in the embedded `_vcData` global
2564
+ // (baked directly into the bundle at build time — see fcs.js's `vt`).
2565
+ // Resolved fresh on every call, not cached at module-load time: fcs.js
2566
+ // hands `vt` (which declares `_vcData`) to a remote endpoint that
2567
+ // weaves it into the final bundle at a position this code can't see or
2568
+ // control, so there's no reliable "by now it's definitely defined"
2569
+ // point earlier than an actual function call like this one — by the
2570
+ // time any event-driven code (this function) runs, all synchronous
2571
+ // top-level script content, wherever the remote endpoint placed it,
2572
+ // has already executed.
2573
+ let _payload = _vcD || (typeof _vcData !== 'undefined' ? _vcData : undefined);
2574
+
2575
+ // HST wire-format version check (2026-09-15) — see fcs.js's own
2576
+ // HST_FORMAT_VERSION comment for the full rationale. Checked once per
2577
+ // payload object (not once per call) via a property stamped directly
2578
+ // onto it, so a hot-reloaded payload during dev gets re-checked too.
2579
+ if (_payload && !_payload.__hstVersionChecked) {
2580
+ _payload.__hstVersionChecked = true;
2581
+ if (_payload.hstFormatVersion !== undefined && _payload.hstFormatVersion !== EXPECTED_HST_FORMAT_VERSION) {
2582
+ reportLumenError({
2583
+ stage: 'hst-version-mismatch',
2584
+ error: new Error('This project was compiled for HST format v' + _payload.hstFormatVersion + ', but this LumenJS runtime expects v' + EXPECTED_HST_FORMAT_VERSION + '. @lmjs/cli and @lmjs/core are out of sync — reinstall/upgrade both together.'),
2585
+ });
2586
+ return;
2587
+ }
2588
+ }
2589
+
2590
+ if ((_payload && _payload[type].hasOwnProperty(fileKey)) /*&& _jswacrk.isStarted()*/ && _csswrk.isStarted()) {
2591
+ if (isSub) cl("Rendering", n, fileKey);
2592
+ var hst = _payload[type][fileKey];
2593
+ // var mxes = hst.mxes;
2594
+ // hst = hst.hst;
2595
+ // cl("HHHSSSTTT", hst);
2596
+ // return;
2597
+
2598
+ if (isSub) {
2599
+ // 2026-09-17: per-instance subview scoping — each matched
2600
+ // element needs ITS OWN index within the search array (not its
2601
+ // position in this filtered-by-subPath list) so its compiled
2602
+ // script rewrites into `<scopeBase>.views[<that exact index>].vars`,
2603
+ // not the flat `_vt.View.vars` every subview used to collide on.
2604
+ // Searches viewsArr (this main view's _vt.View.views by
2605
+ // default — unchanged for the top-level caller) or, when
2606
+ // called recursively from a subview instance's own
2607
+ // updateVXRs(), THAT instance's own `.views` — see
2608
+ // updateVXRs() for why nested subviews need this.
2609
+ let searchArr = viewsArr || _vt.View.views;
2610
+ let els = [];
2611
+ for (let i = 0; i < searchArr.length; i++) {
2612
+ let _el = searchArr[i];
2613
+ if (_el.__isProxy) _el = _el.target;
2614
+ if (_el.subPath == n) els.push({ el: _el, viewsIndex: i });
2615
+ }
2616
+ if (els.length) {
2617
+ // cl(els);
2618
+ for (let i = 0; i < els.length; i++) {
2619
+ const { el, viewsIndex } = els[i];
2620
+ // Create/reuse el.vars BEFORE rendering, so the fresh
2621
+ // _v instance renderHST() constructs uses this EXACT
2622
+ // object as its own .vars — see renderHST()'s ownVars
2623
+ // param comment for why that identity matters.
2624
+ el.vars = el.vars || {};
2625
+ el.views = el.views || [];
2626
+ el.fns = el.fns || {};
2627
+ let _re = await renderHST(hst, n, 'sub', undefined, null, scopeBase.concat(["views", viewsIndex]), el.vars, el.fns, el.views);
2628
+ el._re = _re;
2629
+ el.innerHTML = "";
2630
+ // cl(el);
2631
+ el.append(..._re._RealDOM);
2632
+ _re.renderAll();
2633
+ }
2634
+ // _re._RealDOM.forEach(_el => el[0].appendChild(_el))
2635
+ // cl(_re);
2636
+ }
2637
+ } else {
2638
+ if (type == 'layouts') {
2639
+ let appSelector = appSettings.App ?? "[app]";
2640
+ var appContainer = $(appSelector);
2641
+ if (appContainer.length) {
2642
+ let _rel = await renderHST(hst, n, 'layout');
2643
+ appContainer.data('layout', n).html(_rel._RealDOM);
2644
+ _rel.renderAll();
2645
+ goToNode();
2646
+ }
2647
+ } else {
2648
+ let _re = await renderHST(hst, n, 'main');
2649
+ // cl("RRR", _re);
2650
+ let layout = _re.view.settings.layout;
2651
+
2652
+ let filePathL = "src/layouts/" + layout + ".layout";
2653
+ let fileKeyL = btoa(filePathL);
2654
+ let hstL = _payload['layouts'][fileKeyL];
2655
+ // cl(_rel);
2656
+
2657
+
2658
+ let appSelector = appSettings.App ?? "[app]";
2659
+ var appContainer = $(appSelector);
2660
+ if (appContainer.length) {
2661
+ let currentLayout = $(appSelector).data('layout');
2662
+ if (currentLayout != layout) {
2663
+ let _rel = await renderHST(hstL, layout, 'layout');
2664
+ appContainer.data('layout', layout).html(_rel._RealDOM);
2665
+ _rel.renderAll();
2666
+ } else {
2667
+ // cl("Same Layout");
2668
+ }
2669
+ } else {
2670
+ $("body").prepend("<div " + (appSelector) + "></div>");
2671
+ let _rel = await renderHST(hstL, layout, 'layout');
2672
+ appContainer = $(appSelector);
2673
+ appContainer.data('layout', layout).html(_rel._RealDOM);
2674
+ _rel.renderAll();
2675
+ }
2676
+
2677
+ // return;
2678
+ // setLayout();
2679
+ $('[body]').html(_re._RealDOM);
2680
+
2681
+ // Layout slots (2026-09-15) — see walk()'s "slot" branch
2682
+ // above for how _re.view._slots gets populated. Injected
2683
+ // the same way [body] is, right above — .html() moves the
2684
+ // already-walked real DOM nodes (doesn't clone them), so
2685
+ // this can run before or after renderAll() without
2686
+ // affecting which node each reactive effect targets;
2687
+ // matching [body]'s own ordering here just for consistency.
2688
+ // A view targeting a slot name no loaded layout declares
2689
+ // is a legitimate, silent no-op (the layout may simply not
2690
+ // have that optional region) — $('[slot="x"]') matching
2691
+ // nothing is real jQuery/dom-shim behavior already, not a
2692
+ // new case to handle.
2693
+ if (_re.view._slots) {
2694
+ for (const slotName in _re.view._slots) {
2695
+ if (Object.prototype.hasOwnProperty.call(_re.view._slots, slotName)) {
2696
+ $('[slot="' + slotName + '"]').html(_re.view._slots[slotName]);
2697
+ }
2698
+ }
2699
+ }
2700
+
2701
+ _re.renderAll();
2702
+ }
2703
+ }
2704
+ } else {
2705
+ setTimeout(() => {
2706
+ renderView(n, isSub, d);
2707
+ }, 10);
2708
+ }
2709
+ }