bitwrench 2.0.15 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +57 -21
  2. package/dist/bitwrench-bccl.cjs.js +3750 -0
  3. package/dist/bitwrench-bccl.cjs.min.js +40 -0
  4. package/dist/bitwrench-bccl.esm.js +3745 -0
  5. package/dist/bitwrench-bccl.esm.min.js +40 -0
  6. package/dist/bitwrench-bccl.umd.js +3756 -0
  7. package/dist/bitwrench-bccl.umd.min.js +40 -0
  8. package/dist/bitwrench-code-edit.cjs.js +57 -7
  9. package/dist/bitwrench-code-edit.cjs.min.js +9 -2
  10. package/dist/bitwrench-code-edit.es5.js +74 -11
  11. package/dist/bitwrench-code-edit.es5.min.js +9 -2
  12. package/dist/bitwrench-code-edit.esm.js +57 -7
  13. package/dist/bitwrench-code-edit.esm.min.js +9 -2
  14. package/dist/bitwrench-code-edit.umd.js +57 -7
  15. package/dist/bitwrench-code-edit.umd.min.js +9 -2
  16. package/dist/bitwrench-lean.cjs.js +905 -157
  17. package/dist/bitwrench-lean.cjs.min.js +7 -7
  18. package/dist/bitwrench-lean.es5.js +931 -157
  19. package/dist/bitwrench-lean.es5.min.js +5 -5
  20. package/dist/bitwrench-lean.esm.js +904 -157
  21. package/dist/bitwrench-lean.esm.min.js +7 -7
  22. package/dist/bitwrench-lean.umd.js +905 -157
  23. package/dist/bitwrench-lean.umd.min.js +7 -7
  24. package/dist/bitwrench.cjs.js +910 -158
  25. package/dist/bitwrench.cjs.min.js +8 -8
  26. package/dist/bitwrench.css +60 -17
  27. package/dist/bitwrench.es5.js +939 -158
  28. package/dist/bitwrench.es5.min.js +6 -6
  29. package/dist/bitwrench.esm.js +909 -158
  30. package/dist/bitwrench.esm.min.js +8 -8
  31. package/dist/bitwrench.min.css +1 -1
  32. package/dist/bitwrench.umd.js +910 -158
  33. package/dist/bitwrench.umd.min.js +8 -8
  34. package/dist/builds.json +168 -80
  35. package/dist/bwserve.cjs.js +660 -0
  36. package/dist/bwserve.esm.js +652 -0
  37. package/dist/sri.json +36 -28
  38. package/package.json +20 -3
  39. package/readme.html +62 -23
  40. package/src/bitwrench-bccl-entry.js +72 -0
  41. package/src/bitwrench-bccl.js +5 -1
  42. package/src/bitwrench-code-edit.js +56 -6
  43. package/src/bitwrench-color-utils.js +5 -6
  44. package/src/bitwrench-styles.js +20 -8
  45. package/src/bitwrench.js +876 -140
  46. package/src/bwserve/client.js +182 -0
  47. package/src/bwserve/index.js +363 -0
  48. package/src/bwserve/shell.js +106 -0
  49. package/src/cli/index.js +36 -15
  50. package/src/cli/layout-default.js +47 -32
  51. package/src/cli/serve.js +325 -0
  52. package/src/version.js +3 -3
  53. /package/bin/{bitwrench.js → bwcli.js} +0 -0
@@ -1,10 +1,11 @@
1
- /*! bitwrench-lean v2.0.15 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-lean v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4
4
  typeof define === 'function' && define.amd ? define(factory) :
5
5
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bw = factory());
6
6
  })(this, (function () { 'use strict';
7
7
 
8
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
8
9
  function _arrayLikeToArray(r, a) {
9
10
  (null == a || a > r.length) && (a = r.length);
10
11
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
@@ -189,14 +190,14 @@
189
190
  */
190
191
 
191
192
  var VERSION_INFO = {
192
- version: '2.0.15',
193
+ version: '2.0.17',
193
194
  name: 'bitwrench',
194
195
  description: 'A library for javascript UI functions.',
195
196
  license: 'BSD-2-Clause',
196
197
  homepage: 'https://deftio.github.com/bitwrench/pages',
197
198
  repository: 'git+https://github.com/deftio/bitwrench.git',
198
199
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
199
- buildDate: '2026-03-10T09:08:17.015Z'
200
+ buildDate: '2026-03-13T23:15:10.823Z'
200
201
  };
201
202
 
202
203
  /**
@@ -609,12 +610,11 @@
609
610
  var lightBase = config.light || hslToHex([h, 8, 97]);
610
611
  var darkBase = config.dark || hslToHex([h, 10, 13]);
611
612
 
612
- // Background & surface tokens — default to light (white/near-white).
613
- // Dark backgrounds require explicit config.background / config.surface.
614
- // Primary/secondary colors are accents, not page backgrounds, so
615
- // isLightPalette should NOT drive bg/surface defaults.
616
- var bgBase = config.background || '#ffffff';
617
- var surfBase = config.surface || '#f8f9fa';
613
+ // Background & surface tokens — tinted with primary hue for theme personality.
614
+ // Very subtle: bg at L=98/S=6, surface at L=96/S=8.
615
+ // User can override with config.background / config.surface.
616
+ var bgBase = config.background || hslToHex([h, 6, 98]);
617
+ var surfBase = config.surface || hslToHex([h, 8, 96]);
618
618
  var palette = {
619
619
  primary: deriveShades(config.primary),
620
620
  secondary: deriveShades(config.secondary),
@@ -1857,8 +1857,8 @@
1857
1857
  },
1858
1858
  '.bw_container_fluid': {
1859
1859
  'width': '100%',
1860
- 'padding-right': '15px',
1861
- 'padding-left': '15px',
1860
+ 'padding-right': '0.75rem',
1861
+ 'padding-left': '0.75rem',
1862
1862
  'margin-right': 'auto',
1863
1863
  'margin-left': 'auto'
1864
1864
  },
@@ -2234,7 +2234,9 @@
2234
2234
  'line-height': '1.3',
2235
2235
  'text-align': 'center',
2236
2236
  'white-space': 'nowrap',
2237
- 'vertical-align': 'baseline'
2237
+ 'vertical-align': 'baseline',
2238
+ 'padding': '0.35rem 0.65rem',
2239
+ 'border-radius': '0.25rem'
2238
2240
  },
2239
2241
  '.bw_badge:empty': {
2240
2242
  'display': 'none'
@@ -2699,7 +2701,8 @@
2699
2701
  '.bw_code_pre': {
2700
2702
  'margin': '0',
2701
2703
  'border': 'none',
2702
- 'overflow-x': 'auto'
2704
+ 'overflow-x': 'auto',
2705
+ 'max-width': '100%'
2703
2706
  },
2704
2707
  '.bw_code_block': {
2705
2708
  'display': 'block',
@@ -2928,7 +2931,7 @@
2928
2931
  },
2929
2932
  '.bw_modal_dialog': {
2930
2933
  'position': 'relative',
2931
- 'width': '100%',
2934
+ 'width': 'calc(100% - 1rem)',
2932
2935
  'max-width': '500px',
2933
2936
  'margin': '1.75rem auto',
2934
2937
  'pointer-events': 'none'
@@ -3017,7 +3020,7 @@
3017
3020
  '.bw_toast': {
3018
3021
  'pointer-events': 'auto',
3019
3022
  'width': '350px',
3020
- 'max-width': '100%',
3023
+ 'max-width': 'calc(100vw - 2rem)',
3021
3024
  'background-clip': 'padding-box',
3022
3025
  'opacity': '0'
3023
3026
  },
@@ -3224,6 +3227,7 @@
3224
3227
  'z-index': '999',
3225
3228
  'font-size': '0.875rem',
3226
3229
  'white-space': 'nowrap',
3230
+ 'max-width': 'min(300px, calc(100vw - 1rem))',
3227
3231
  'pointer-events': 'none',
3228
3232
  'opacity': '0',
3229
3233
  'visibility': 'hidden'
@@ -3282,7 +3286,7 @@
3282
3286
  'position': 'absolute',
3283
3287
  'z-index': '1000',
3284
3288
  'min-width': '200px',
3285
- 'max-width': '320px',
3289
+ 'max-width': 'min(320px, calc(100vw - 2rem))',
3286
3290
  'pointer-events': 'none',
3287
3291
  'opacity': '0',
3288
3292
  'visibility': 'hidden'
@@ -3828,6 +3832,45 @@
3828
3832
  },
3829
3833
  '.bw_feature_grid, .bw_feature-grid': {
3830
3834
  'grid-template-columns': '1fr'
3835
+ },
3836
+ '.bw_modal_dialog': {
3837
+ 'margin': '0.5rem auto'
3838
+ },
3839
+ '.bw_modal_lg': {
3840
+ 'max-width': 'calc(100% - 1rem)'
3841
+ },
3842
+ '.bw_modal_xl': {
3843
+ 'max-width': 'calc(100% - 1rem)'
3844
+ },
3845
+ '.bw_navbar': {
3846
+ 'padding': '0.5rem 0.75rem'
3847
+ },
3848
+ '.bw_navbar_brand': {
3849
+ 'margin-right': '0.5rem',
3850
+ 'font-size': '1rem'
3851
+ },
3852
+ '.bw_navbar_nav': {
3853
+ 'flex-wrap': 'wrap'
3854
+ },
3855
+ '.bw_tooltip': {
3856
+ 'white-space': 'normal'
3857
+ },
3858
+ '.bw_table': {
3859
+ 'display': 'block',
3860
+ 'overflow-x': 'auto',
3861
+ '-webkit-overflow-scrolling': 'touch'
3862
+ },
3863
+ '.bw_col, .bw_col_1, .bw_col_2, .bw_col_3, .bw_col_4, .bw_col_5, .bw_col_6, .bw_col_7, .bw_col_8, .bw_col_9, .bw_col_10, .bw_col_11, .bw_col_12': {
3864
+ 'flex': '0 0 100%',
3865
+ 'max-width': '100%'
3866
+ },
3867
+ '.bw_container': {
3868
+ 'padding-right': '0.5rem',
3869
+ 'padding-left': '0.5rem'
3870
+ },
3871
+ '.bw_container_fluid': {
3872
+ 'padding-right': '0.5rem',
3873
+ 'padding-left': '0.5rem'
3831
3874
  }
3832
3875
  }
3833
3876
  }
@@ -5083,7 +5126,7 @@
5083
5126
  __monkey_patch_is_nodejs__: {
5084
5127
  _value: 'ignore',
5085
5128
  set: function set(x) {
5086
- this._value = typeof x === 'boolean' ? x : 'ignore';
5129
+ this._value = _is(x, 'boolean') ? x : 'ignore';
5087
5130
  },
5088
5131
  get: function get() {
5089
5132
  return this._value;
@@ -5131,6 +5174,76 @@
5131
5174
  configurable: true
5132
5175
  });
5133
5176
 
5177
+ // ── Internal aliases ─────────────────────────────────────────────────────
5178
+ // Short names for frequently-used builtins and internal methods.
5179
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
5180
+ //
5181
+ // Why: Terser can't shorten global property chains (console.warn,
5182
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
5183
+ // because it can't prove they're side-effect-free. We can, so we alias
5184
+ // them here. Each alias saves bytes in the minified output, and the short
5185
+ // names also reduce visual noise in the hot paths (binding pipeline,
5186
+ // createDOM, etc.).
5187
+ //
5188
+ // Alias Target Sites
5189
+ // ───────── ────────────────────────────────────── ─────
5190
+ // _hop Object.prototype.hasOwnProperty 15
5191
+ // _isA Array.isArray 25
5192
+ // _keys Object.keys 7
5193
+ // _to bw.typeOf (type string) 26
5194
+ // _is type check boolean: _is(x,'string') ~50
5195
+ // _cw console.warn 8
5196
+ // _cl console.log 11
5197
+ // _ce console.error 4
5198
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
5199
+ //
5200
+ // Note: document.createElement etc. are NOT aliased because they require
5201
+ // `this === document` and .bind() would add overhead on every call.
5202
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
5203
+ // patching of console.warn/log/error continues to work.
5204
+ //
5205
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
5206
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
5207
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
5208
+ //
5209
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
5210
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
5211
+ // returns trueVal if _to(x)===type, else falseVal.
5212
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
5213
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
5214
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
5215
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
5216
+ // Uncomment if pattern frequency justifies them:
5217
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
5218
+ // var _toc = function(x, t, y, n) { var r = _to(x)===t; return r ? (_to(y)==='function'?y(x):y) : (_to(n)==='function'?n(x):n); };
5219
+ // ─────────────────────────────────────────────────────────────────────────
5220
+ var _hop = Object.prototype.hasOwnProperty;
5221
+ var _isA = Array.isArray;
5222
+ var _keys = Object.keys;
5223
+ var _to = typeOf; // imported from bitwrench-utils.js
5224
+ var _is = function _is(x, t) {
5225
+ var r = _to(x);
5226
+ return r === t || r.toLowerCase() === t;
5227
+ };
5228
+ // Console aliases use thin wrappers (not direct references) so that test
5229
+ // code can monkey-patch console.warn/log/error and the patches take effect.
5230
+ var _cw = function _cw() {
5231
+ console.warn.apply(console, arguments);
5232
+ };
5233
+ var _cl = function _cl() {
5234
+ console.log.apply(console, arguments);
5235
+ };
5236
+ var _ce = function _ce() {
5237
+ console.error.apply(console, arguments);
5238
+ };
5239
+
5240
+ /**
5241
+ * Debug flag. When true, emits console.warn for silent binding failures
5242
+ * (missing paths, null refs, auto-created intermediate objects).
5243
+ * @type {boolean}
5244
+ */
5245
+ bw.debug = false;
5246
+
5134
5247
  /**
5135
5248
  * Lazy-resolve Node.js `fs` module.
5136
5249
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -5280,7 +5393,7 @@
5280
5393
  */
5281
5394
  bw._el = function (id) {
5282
5395
  // Pass-through for DOM elements
5283
- if (typeof id !== 'string') return id || null;
5396
+ if (!_is(id, 'string')) return id || null;
5284
5397
  if (!id) return null;
5285
5398
  if (!bw._isBrowser) return null;
5286
5399
 
@@ -5375,7 +5488,7 @@
5375
5488
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
5376
5489
  */
5377
5490
  bw.escapeHTML = function (str) {
5378
- if (typeof str !== 'string') return '';
5491
+ if (!_is(str, 'string')) return '';
5379
5492
  var escapeMap = {
5380
5493
  '&': '&amp;',
5381
5494
  '<': '&lt;',
@@ -5452,7 +5565,7 @@
5452
5565
  }
5453
5566
 
5454
5567
  // Handle arrays of TACOs
5455
- if (Array.isArray(taco)) {
5568
+ if (_isA(taco)) {
5456
5569
  return taco.map(function (t) {
5457
5570
  return bw.html(t, options);
5458
5571
  }).join('');
@@ -5475,17 +5588,17 @@
5475
5588
  if (taco && taco._bwEach && options.state) {
5476
5589
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
5477
5590
  var arr = bw._evaluatePath(options.state, eachExpr);
5478
- if (!Array.isArray(arr)) return '';
5591
+ if (!_isA(arr)) return '';
5479
5592
  return arr.map(function (item, idx) {
5480
5593
  return bw.html(taco.factory(item, idx), options);
5481
5594
  }).join('');
5482
5595
  }
5483
5596
 
5484
5597
  // Handle primitives and non-TACO objects
5485
- if (_typeof(taco) !== 'object' || !taco.t) {
5598
+ if (!_is(taco, 'object') || !taco.t) {
5486
5599
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
5487
5600
  // Resolve template bindings if state provided
5488
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
5601
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
5489
5602
  str = bw._resolveTemplate(str, options.state, !!options.compile);
5490
5603
  }
5491
5604
  return str;
@@ -5510,9 +5623,17 @@
5510
5623
  // Skip null, undefined, false
5511
5624
  if (value == null || value === false) continue;
5512
5625
 
5513
- // Skip event handlers (they're for DOM only)
5514
- if (key.startsWith('on')) continue;
5515
- if (key === 'style' && _typeof(value) === 'object') {
5626
+ // Serialize event handlers via funcRegister
5627
+ if (key.startsWith('on')) {
5628
+ if (_is(value, 'function')) {
5629
+ var fnId = bw.funcRegister(value);
5630
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
5631
+ } else if (_is(value, 'string')) {
5632
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
5633
+ }
5634
+ continue;
5635
+ }
5636
+ if (key === 'style' && _is(value, 'object')) {
5516
5637
  // Convert style object to string
5517
5638
  var styleStr = Object.entries(value).filter(function (_ref) {
5518
5639
  var _ref2 = _slicedToArray(_ref, 2),
@@ -5529,7 +5650,7 @@
5529
5650
  }
5530
5651
  } else if (key === 'class') {
5531
5652
  // Handle class as array or string
5532
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
5653
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
5533
5654
  if (classStr) {
5534
5655
  attrStr += " class=\"".concat(bw.escapeHTML(classStr), "\"");
5535
5656
  }
@@ -5565,12 +5686,184 @@
5565
5686
  // Process content recursively
5566
5687
  var contentStr = content != null ? bw.html(content, options) : '';
5567
5688
  // Resolve template bindings in content if state provided
5568
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
5689
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
5569
5690
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
5570
5691
  }
5571
5692
  return "<".concat(tag).concat(attrStr, ">").concat(contentStr, "</").concat(tag, ">");
5572
5693
  };
5573
5694
 
5695
+ /**
5696
+ * Generate a complete, self-contained HTML document from TACO content.
5697
+ *
5698
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
5699
+ * func registry emission (so serialized event handlers work), optional theme,
5700
+ * and extra head elements. Designed for static site generation, offline/airgapped
5701
+ * use, and the "static site that isn't static" workflow.
5702
+ *
5703
+ * @param {Object} [opts={}] - Page options
5704
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
5705
+ * @param {string} [opts.title='bitwrench'] - Page title
5706
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
5707
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
5708
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
5709
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
5710
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
5711
+ * @param {string} [opts.favicon=''] - Favicon URL
5712
+ * @param {string} [opts.lang='en'] - HTML lang attribute
5713
+ * @returns {string} Complete HTML document string
5714
+ * @category DOM Generation
5715
+ * @see bw.html
5716
+ * @example
5717
+ * bw.htmlPage({
5718
+ * title: 'My App',
5719
+ * body: { t: 'h1', c: 'Hello World' },
5720
+ * runtime: 'shim'
5721
+ * })
5722
+ */
5723
+ bw.htmlPage = function (opts) {
5724
+ opts = opts || {};
5725
+ var title = opts.title || 'bitwrench';
5726
+ var body = opts.body || '';
5727
+ var state = opts.state || undefined;
5728
+ var runtime = opts.runtime || 'shim';
5729
+ var css = opts.css || '';
5730
+ var theme = opts.theme || null;
5731
+ var headExtra = opts.head || [];
5732
+ var favicon = opts.favicon || '';
5733
+ var lang = opts.lang || 'en';
5734
+
5735
+ // Snapshot funcRegistry counter before rendering
5736
+ var fnCounterBefore = bw._fnIDCounter;
5737
+
5738
+ // Render body content
5739
+ var bodyHTML = '';
5740
+ if (_is(body, 'string')) {
5741
+ bodyHTML = body;
5742
+ } else {
5743
+ var htmlOpts = {};
5744
+ if (state) htmlOpts.state = state;
5745
+ bodyHTML = bw.html(body, htmlOpts);
5746
+ }
5747
+
5748
+ // Collect functions registered during this render
5749
+ var fnCounterAfter = bw._fnIDCounter;
5750
+ var registryEntries = '';
5751
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
5752
+ var fnKey = 'bw_fn_' + i;
5753
+ if (bw._fnRegistry[fnKey]) {
5754
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' + bw._fnRegistry[fnKey].toString() + ';\n';
5755
+ }
5756
+ }
5757
+
5758
+ // Build runtime script for <head>
5759
+ var runtimeHead = '';
5760
+ if (runtime === 'inline') {
5761
+ // Read UMD bundle synchronously if in Node.js
5762
+ var umdSource = null;
5763
+ if (bw._isNode) {
5764
+ try {
5765
+ var fs = typeof require === 'function' ? require('fs') : null;
5766
+ var pathMod = typeof require === 'function' ? require('path') : null;
5767
+ if (fs && pathMod) {
5768
+ // Resolve dist/ relative to this source file
5769
+ var srcDir = '';
5770
+ try {
5771
+ srcDir = pathMod.dirname(typeof __filename !== 'undefined' ? __filename : '');
5772
+ } catch (e2) {/* ESM: __filename not available */}
5773
+ if (!srcDir && typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.es5.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.es5.js', document.baseURI).href))) {
5774
+ var url = typeof require === 'function' ? require('url') : null;
5775
+ if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath((typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.es5.js', document.baseURI).href))));
5776
+ }
5777
+ if (srcDir) {
5778
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
5779
+ umdSource = fs.readFileSync(distPath, 'utf8');
5780
+ }
5781
+ }
5782
+ } catch (e) {/* fall through */}
5783
+ }
5784
+ if (umdSource) {
5785
+ runtimeHead = '<script>' + umdSource + '</script>';
5786
+ } else {
5787
+ // Fallback to shim in browser or if dist not available
5788
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
5789
+ }
5790
+ } else if (runtime === 'cdn') {
5791
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
5792
+ } else if (runtime === 'shim') {
5793
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
5794
+ }
5795
+ // runtime === 'none' → empty
5796
+
5797
+ // Theme CSS
5798
+ var themeCSS = '';
5799
+ if (theme) {
5800
+ var themeConfig = _is(theme, 'string') ? THEME_PRESETS[theme.toLowerCase()] || null : theme;
5801
+ if (themeConfig) {
5802
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, {
5803
+ inject: false
5804
+ }));
5805
+ themeCSS = themeResult.css;
5806
+ }
5807
+ }
5808
+
5809
+ // Extra <head> elements
5810
+ var headHTML = '';
5811
+ if (_isA(headExtra) && headExtra.length > 0) {
5812
+ headHTML = headExtra.map(function (el) {
5813
+ return bw.html(el);
5814
+ }).join('\n');
5815
+ }
5816
+
5817
+ // Favicon
5818
+ var faviconTag = '';
5819
+ if (favicon) {
5820
+ var safeFavicon = favicon.replace(/[&<>"']/g, function (c) {
5821
+ return {
5822
+ '&': '&amp;',
5823
+ '<': '&lt;',
5824
+ '>': '&gt;',
5825
+ '"': '&quot;',
5826
+ "'": '&#39;'
5827
+ }[c];
5828
+ });
5829
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
5830
+ }
5831
+
5832
+ // Escaped title
5833
+ var safeTitle = bw.escapeHTML(title);
5834
+
5835
+ // Combine all CSS
5836
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
5837
+
5838
+ // Body-end script: registry entries + optional loadDefaultStyles
5839
+ var bodyEndScript = '';
5840
+ var bodyEndParts = [];
5841
+ if (registryEntries) {
5842
+ bodyEndParts.push(registryEntries);
5843
+ }
5844
+ if (runtime === 'inline' || runtime === 'cdn') {
5845
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
5846
+ }
5847
+ if (bodyEndParts.length > 0) {
5848
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
5849
+ }
5850
+
5851
+ // Assemble document
5852
+ var parts = ['<!DOCTYPE html>', '<html lang="' + lang + '">', '<head>', '<meta charset="UTF-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">'];
5853
+ parts.push('<title>' + safeTitle + '</title>');
5854
+ if (faviconTag) parts.push(faviconTag);
5855
+ if (runtimeHead) parts.push(runtimeHead);
5856
+ if (headHTML) parts.push(headHTML);
5857
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
5858
+ parts.push('</head>');
5859
+ parts.push('<body>');
5860
+ parts.push(bodyHTML);
5861
+ if (bodyEndScript) parts.push(bodyEndScript);
5862
+ parts.push('</body>');
5863
+ parts.push('</html>');
5864
+ return parts.join('\n');
5865
+ };
5866
+
5574
5867
  /**
5575
5868
  * Create a live DOM element from a TACO object (browser only).
5576
5869
  *
@@ -5616,7 +5909,7 @@
5616
5909
  }
5617
5910
 
5618
5911
  // Handle text nodes
5619
- if (_typeof(taco) !== 'object' || !taco.t) {
5912
+ if (!_is(taco, 'object') || !taco.t) {
5620
5913
  return document.createTextNode(String(taco));
5621
5914
  }
5622
5915
  var tag = taco.t,
@@ -5635,16 +5928,16 @@
5635
5928
  key = _Object$entries2$_i[0],
5636
5929
  value = _Object$entries2$_i[1];
5637
5930
  if (value == null || value === false) continue;
5638
- if (key === 'style' && _typeof(value) === 'object') {
5931
+ if (key === 'style' && _is(value, 'object')) {
5639
5932
  // Apply styles directly
5640
5933
  Object.assign(el.style, value);
5641
5934
  } else if (key === 'class') {
5642
5935
  // Handle class as array or string
5643
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
5936
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
5644
5937
  if (classStr) {
5645
5938
  el.className = classStr;
5646
5939
  }
5647
- } else if (key.startsWith('on') && typeof value === 'function') {
5940
+ } else if (key.startsWith('on') && _is(value, 'function')) {
5648
5941
  // Event handlers
5649
5942
  var eventName = key.slice(2).toLowerCase();
5650
5943
  el.addEventListener(eventName, value);
@@ -5664,7 +5957,7 @@
5664
5957
  // Children with data-bw_id or id attributes get local refs on the parent,
5665
5958
  // so o.render functions can access them without any DOM lookup.
5666
5959
  if (content != null) {
5667
- if (Array.isArray(content)) {
5960
+ if (_isA(content)) {
5668
5961
  content.forEach(function (child) {
5669
5962
  if (child != null) {
5670
5963
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -5684,20 +5977,20 @@
5684
5977
  if (childEl._bw_refs) {
5685
5978
  if (!el._bw_refs) el._bw_refs = {};
5686
5979
  for (var rk in childEl._bw_refs) {
5687
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
5980
+ if (_hop.call(childEl._bw_refs, rk)) {
5688
5981
  el._bw_refs[rk] = childEl._bw_refs[rk];
5689
5982
  }
5690
5983
  }
5691
5984
  }
5692
5985
  }
5693
5986
  });
5694
- } else if (_typeof(content) === 'object' && content.__bw_raw) {
5987
+ } else if (_is(content, 'object') && content.__bw_raw) {
5695
5988
  // Raw HTML content — inject via innerHTML
5696
5989
  el.innerHTML = content.v;
5697
5990
  } else if (content._bwComponent === true) {
5698
5991
  // Single ComponentHandle as content
5699
5992
  content.mount(el);
5700
- } else if (_typeof(content) === 'object' && content.t) {
5993
+ } else if (_is(content, 'object') && content.t) {
5701
5994
  var childEl = bw.createDOM(content, options);
5702
5995
  el.appendChild(childEl);
5703
5996
  var childBwId = content.a ? content.a['data-bw_id'] || content.a.id : null;
@@ -5708,7 +6001,7 @@
5708
6001
  if (childEl._bw_refs) {
5709
6002
  if (!el._bw_refs) el._bw_refs = {};
5710
6003
  for (var rk in childEl._bw_refs) {
5711
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
6004
+ if (_hop.call(childEl._bw_refs, rk)) {
5712
6005
  el._bw_refs[rk] = childEl._bw_refs[rk];
5713
6006
  }
5714
6007
  }
@@ -5740,7 +6033,7 @@
5740
6033
  if (opts.render) {
5741
6034
  el._bw_render = opts.render;
5742
6035
  if (opts.mounted) {
5743
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
6036
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
5744
6037
  }
5745
6038
 
5746
6039
  // Queue initial render (same timing as mounted)
@@ -5812,7 +6105,7 @@
5812
6105
  // Get target element (use cache-backed lookup)
5813
6106
  var targetEl = bw._el(target);
5814
6107
  if (!targetEl) {
5815
- console.error('bw.DOM: Target element not found:', target);
6108
+ _ce('bw.DOM: Target element not found:', target);
5816
6109
  return null;
5817
6110
  }
5818
6111
 
@@ -5850,7 +6143,7 @@
5850
6143
  targetEl.appendChild(taco.element);
5851
6144
  }
5852
6145
  // Handle arrays
5853
- else if (Array.isArray(taco)) {
6146
+ else if (_isA(taco)) {
5854
6147
  taco.forEach(function (t) {
5855
6148
  if (t != null) {
5856
6149
  if (t._bwComponent === true) {
@@ -5885,7 +6178,7 @@
5885
6178
  bw.compileProps = function (handle) {
5886
6179
  var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
5887
6180
  var compiledProps = {};
5888
- Object.keys(props).forEach(function (key) {
6181
+ _keys(props).forEach(function (key) {
5889
6182
  // Create getter/setter for each prop
5890
6183
  Object.defineProperty(compiledProps, key, {
5891
6184
  get: function get() {
@@ -6196,17 +6489,17 @@
6196
6489
  if (attr) {
6197
6490
  // Patch an attribute
6198
6491
  el.setAttribute(attr, String(content));
6199
- } else if (Array.isArray(content)) {
6492
+ } else if (_isA(content)) {
6200
6493
  // Patch with array of children (strings and/or TACOs)
6201
6494
  el.innerHTML = '';
6202
6495
  content.forEach(function (item) {
6203
- if (typeof item === 'string' || typeof item === 'number') {
6496
+ if (_is(item, 'string') || _is(item, 'number')) {
6204
6497
  el.appendChild(document.createTextNode(String(item)));
6205
6498
  } else if (item && item.t) {
6206
6499
  el.appendChild(bw.createDOM(item));
6207
6500
  }
6208
6501
  });
6209
- } else if (_typeof(content) === 'object' && content !== null && content.t) {
6502
+ } else if (_is(content, 'object') && content.t) {
6210
6503
  // Patch with a TACO — replace children
6211
6504
  el.innerHTML = '';
6212
6505
  el.appendChild(bw.createDOM(content));
@@ -6237,7 +6530,7 @@
6237
6530
  bw.patchAll = function (patches) {
6238
6531
  var results = {};
6239
6532
  for (var id in patches) {
6240
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
6533
+ if (_hop.call(patches, id)) {
6241
6534
  results[id] = bw.patch(id, patches[id]);
6242
6535
  }
6243
6536
  }
@@ -6334,7 +6627,7 @@
6334
6627
  snapshot[i].handler(detail);
6335
6628
  called++;
6336
6629
  } catch (err) {
6337
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
6630
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
6338
6631
  }
6339
6632
  }
6340
6633
  return called;
@@ -6435,8 +6728,8 @@
6435
6728
  * @see bw.funcGetDispatchStr
6436
6729
  */
6437
6730
  bw.funcRegister = function (fn, name) {
6438
- if (typeof fn !== 'function') return '';
6439
- var fnID = typeof name === 'string' && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
6731
+ if (!_is(fn, 'function')) return '';
6732
+ var fnID = _is(name, 'string') && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
6440
6733
  bw._fnRegistry[fnID] = fn;
6441
6734
  return fnID;
6442
6735
  };
@@ -6455,8 +6748,8 @@
6455
6748
  bw.funcGetById = function (name, errFn) {
6456
6749
  name = String(name);
6457
6750
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
6458
- return typeof errFn === 'function' ? errFn : function () {
6459
- console.warn('bw.funcGetById: unregistered fn "' + name + '"');
6751
+ return _is(errFn, 'function') ? errFn : function () {
6752
+ _cw('bw.funcGetById: unregistered fn "' + name + '"');
6460
6753
  };
6461
6754
  };
6462
6755
 
@@ -6498,13 +6791,23 @@
6498
6791
  bw.funcGetRegistry = function () {
6499
6792
  var copy = {};
6500
6793
  for (var k in bw._fnRegistry) {
6501
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
6794
+ if (_hop.call(bw._fnRegistry, k)) {
6502
6795
  copy[k] = bw._fnRegistry[k];
6503
6796
  }
6504
6797
  }
6505
6798
  return copy;
6506
6799
  };
6507
6800
 
6801
+ /**
6802
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
6803
+ * When embedded in a `<script>` tag, provides just enough infrastructure
6804
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
6805
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
6806
+ * @type {string}
6807
+ * @category Function Registry
6808
+ */
6809
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' + 'if(!bw._fnRegistry)bw._fnRegistry={};' + 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' + 'console.warn("bw: unregistered fn "+n)};};' + 'bw.funcRegister=function(fn,name){' + 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' + 'bw._fnRegistry[id]=fn;return id;};' + 'window.bw=bw;})();';
6810
+
6508
6811
  // ===================================================================================
6509
6812
  // Template Binding Utilities
6510
6813
  // ===================================================================================
@@ -6536,7 +6839,10 @@
6536
6839
  var parts = path.split('.');
6537
6840
  var val = state;
6538
6841
  for (var i = 0; i < parts.length; i++) {
6539
- if (val == null) return '';
6842
+ if (val == null) {
6843
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
6844
+ return '';
6845
+ }
6540
6846
  val = val[parts[i]];
6541
6847
  }
6542
6848
  return val == null ? '' : val;
@@ -6556,7 +6862,7 @@
6556
6862
  */
6557
6863
  bw._compiledExprs = {};
6558
6864
  bw._resolveTemplate = function (str, state, compile) {
6559
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
6865
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
6560
6866
  var bindings = bw._parseBindings(str);
6561
6867
  if (bindings.length === 0) return str;
6562
6868
  var result = '';
@@ -6579,6 +6885,7 @@
6579
6885
  try {
6580
6886
  val = bw._compiledExprs[b.expr](state);
6581
6887
  } catch (e) {
6888
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
6582
6889
  val = '';
6583
6890
  }
6584
6891
  } else {
@@ -6686,7 +6993,7 @@
6686
6993
  this._state = {};
6687
6994
  if (o.state) {
6688
6995
  for (var k in o.state) {
6689
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
6996
+ if (_hop.call(o.state, k)) {
6690
6997
  this._state[k] = o.state[k];
6691
6998
  }
6692
6999
  }
@@ -6695,7 +7002,7 @@
6695
7002
  this._actions = {};
6696
7003
  if (o.actions) {
6697
7004
  for (var k2 in o.actions) {
6698
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
7005
+ if (_hop.call(o.actions, k2)) {
6699
7006
  this._actions[k2] = o.actions[k2];
6700
7007
  }
6701
7008
  }
@@ -6705,7 +7012,7 @@
6705
7012
  if (o.methods) {
6706
7013
  var self = this;
6707
7014
  for (var k3 in o.methods) {
6708
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
7015
+ if (_hop.call(o.methods, k3)) {
6709
7016
  this._methods[k3] = o.methods[k3];
6710
7017
  (function (methodName, methodFn) {
6711
7018
  self[methodName] = function () {
@@ -6738,14 +7045,23 @@
6738
7045
  this._compile = !!o.compile;
6739
7046
  this._bw_refs = {};
6740
7047
  this._refCounter = 0;
7048
+ // Child component ownership (Bug #5)
7049
+ this._children = [];
7050
+ this._parent = null;
7051
+ // Factory metadata for BCCL rebuild (Bug #6)
7052
+ this._factory = taco._bwFactory || null;
6741
7053
  }
6742
7054
 
7055
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
7056
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
7057
+ var _chp = ComponentHandle.prototype;
7058
+
6743
7059
  // ── State Methods ──
6744
7060
 
6745
7061
  /**
6746
7062
  * Get a state value. Dot-path supported: `get('user.name')`
6747
7063
  */
6748
- ComponentHandle.prototype.get = function (key) {
7064
+ _chp.get = function (key) {
6749
7065
  return bw._evaluatePath(this._state, key);
6750
7066
  };
6751
7067
 
@@ -6755,12 +7071,13 @@
6755
7071
  * @param {*} value - New value
6756
7072
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
6757
7073
  */
6758
- ComponentHandle.prototype.set = function (key, value, opts) {
7074
+ _chp.set = function (key, value, opts) {
6759
7075
  // Dot-path set
6760
7076
  var parts = key.split('.');
6761
7077
  var obj = this._state;
6762
7078
  for (var i = 0; i < parts.length - 1; i++) {
6763
- if (obj[parts[i]] == null || _typeof(obj[parts[i]]) !== 'object') {
7079
+ if (!_is(obj[parts[i]], 'object')) {
7080
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
6764
7081
  obj[parts[i]] = {};
6765
7082
  }
6766
7083
  obj = obj[parts[i]];
@@ -6780,10 +7097,10 @@
6780
7097
  /**
6781
7098
  * Get a shallow clone of the full state.
6782
7099
  */
6783
- ComponentHandle.prototype.getState = function () {
7100
+ _chp.getState = function () {
6784
7101
  var clone = {};
6785
7102
  for (var k in this._state) {
6786
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
7103
+ if (_hop.call(this._state, k)) {
6787
7104
  clone[k] = this._state[k];
6788
7105
  }
6789
7106
  }
@@ -6795,9 +7112,9 @@
6795
7112
  * @param {Object} updates - Key-value pairs to merge
6796
7113
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
6797
7114
  */
6798
- ComponentHandle.prototype.setState = function (updates, opts) {
7115
+ _chp.setState = function (updates, opts) {
6799
7116
  for (var k in updates) {
6800
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
7117
+ if (_hop.call(updates, k)) {
6801
7118
  this._state[k] = updates[k];
6802
7119
  this._dirtyKeys[k] = true;
6803
7120
  }
@@ -6814,9 +7131,9 @@
6814
7131
  /**
6815
7132
  * Push a value onto an array in state. Clones the array.
6816
7133
  */
6817
- ComponentHandle.prototype.push = function (key, val) {
7134
+ _chp.push = function (key, val) {
6818
7135
  var arr = this.get(key);
6819
- var newArr = Array.isArray(arr) ? arr.slice() : [];
7136
+ var newArr = _isA(arr) ? arr.slice() : [];
6820
7137
  newArr.push(val);
6821
7138
  this.set(key, newArr);
6822
7139
  };
@@ -6824,9 +7141,9 @@
6824
7141
  /**
6825
7142
  * Splice an array in state. Clones the array.
6826
7143
  */
6827
- ComponentHandle.prototype.splice = function (key, start, deleteCount) {
7144
+ _chp.splice = function (key, start, deleteCount) {
6828
7145
  var arr = this.get(key);
6829
- var newArr = Array.isArray(arr) ? arr.slice() : [];
7146
+ var newArr = _isA(arr) ? arr.slice() : [];
6830
7147
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
6831
7148
  Array.prototype.splice.apply(newArr, args);
6832
7149
  this.set(key, newArr);
@@ -6834,7 +7151,7 @@
6834
7151
 
6835
7152
  // ── Scheduling ──
6836
7153
 
6837
- ComponentHandle.prototype._scheduleDirty = function () {
7154
+ _chp._scheduleDirty = function () {
6838
7155
  if (!this._scheduled) {
6839
7156
  this._scheduled = true;
6840
7157
  bw._dirtyComponents.push(this);
@@ -6849,16 +7166,16 @@
6849
7166
  * Creates binding descriptors with refIds for targeted DOM updates.
6850
7167
  * @private
6851
7168
  */
6852
- ComponentHandle.prototype._compileBindings = function () {
7169
+ _chp._compileBindings = function () {
6853
7170
  this._bindings = [];
6854
7171
  this._refCounter = 0;
6855
- var stateKeys = Object.keys(this._state);
7172
+ var stateKeys = _keys(this._state);
6856
7173
  var self = this;
6857
7174
  function walkTaco(taco, path) {
6858
- if (taco == null || _typeof(taco) !== 'object' || !taco.t) return taco;
7175
+ if (!_is(taco, 'object') || !taco.t) return taco;
6859
7176
 
6860
7177
  // Check content for bindings
6861
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
7178
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
6862
7179
  var refId = 'bw_ref_' + self._refCounter++;
6863
7180
  var parsed = bw._parseBindings(taco.c);
6864
7181
  var deps = [];
@@ -6880,10 +7197,10 @@
6880
7197
  // Check attributes for bindings
6881
7198
  if (taco.a) {
6882
7199
  for (var attrName in taco.a) {
6883
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
7200
+ if (!_hop.call(taco.a, attrName)) continue;
6884
7201
  if (attrName === 'data-bw_ref') continue;
6885
7202
  var attrVal = taco.a[attrName];
6886
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
7203
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
6887
7204
  var refId2 = 'bw_ref_' + self._refCounter++;
6888
7205
  var parsed2 = bw._parseBindings(attrVal);
6889
7206
  var deps2 = [];
@@ -6909,9 +7226,34 @@
6909
7226
  }
6910
7227
 
6911
7228
  // Recurse into children
6912
- if (Array.isArray(taco.c)) {
7229
+ if (_isA(taco.c)) {
6913
7230
  for (var i = 0; i < taco.c.length; i++) {
6914
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
7231
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
7232
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
7233
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
7234
+ var mixedParsed = bw._parseBindings(taco.c[i]);
7235
+ var mixedDeps = [];
7236
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
7237
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
7238
+ }
7239
+ self._bindings.push({
7240
+ expr: taco.c[i],
7241
+ type: 'content',
7242
+ refId: mixedRefId,
7243
+ deps: mixedDeps,
7244
+ template: taco.c[i]
7245
+ });
7246
+ // Replace string with a span wrapper so textContent targets the span only
7247
+ taco.c[i] = {
7248
+ t: 'span',
7249
+ a: {
7250
+ 'data-bw_ref': mixedRefId,
7251
+ style: 'display:contents'
7252
+ },
7253
+ c: taco.c[i]
7254
+ };
7255
+ }
7256
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
6915
7257
  walkTaco(taco.c[i], path.concat(i));
6916
7258
  }
6917
7259
  // Handle bw.when/bw.each markers
@@ -6946,7 +7288,7 @@
6946
7288
  taco.c[i]._refId = eachRefId;
6947
7289
  }
6948
7290
  }
6949
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
7291
+ } else if (_is(taco.c, 'object') && taco.c.t) {
6950
7292
  walkTaco(taco.c, path.concat(0));
6951
7293
  }
6952
7294
  return taco;
@@ -6960,7 +7302,7 @@
6960
7302
  * Build ref map from the live DOM after createDOM.
6961
7303
  * @private
6962
7304
  */
6963
- ComponentHandle.prototype._collectRefs = function () {
7305
+ _chp._collectRefs = function () {
6964
7306
  this._bw_refs = {};
6965
7307
  if (!this.element) return;
6966
7308
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -6981,7 +7323,7 @@
6981
7323
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
6982
7324
  * @param {Element} parentEl - DOM element to mount into
6983
7325
  */
6984
- ComponentHandle.prototype.mount = function (parentEl) {
7326
+ _chp.mount = function (parentEl) {
6985
7327
  // willMount hook
6986
7328
  if (this._hooks.willMount) this._hooks.willMount(this);
6987
7329
 
@@ -7003,7 +7345,7 @@
7003
7345
  // Register named actions in function registry
7004
7346
  var self = this;
7005
7347
  for (var actionName in this._actions) {
7006
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
7348
+ if (_hop.call(this._actions, actionName)) {
7007
7349
  var registeredName = this._bwId + '_' + actionName;
7008
7350
  (function (aName) {
7009
7351
  bw.funcRegister(function (evt) {
@@ -7022,6 +7364,11 @@
7022
7364
  this.element = bw.createDOM(tacoForDOM);
7023
7365
  this.element._bwComponentHandle = this;
7024
7366
  this.element.setAttribute('data-bw_comp_id', this._bwId);
7367
+
7368
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
7369
+ if (this.taco.o && this.taco.o.render) {
7370
+ this.element._bw_render = this.taco.o.render;
7371
+ }
7025
7372
  if (this._userTag) {
7026
7373
  this.element.classList.add(this._userTag);
7027
7374
  }
@@ -7036,6 +7383,16 @@
7036
7383
  this._resolveAndApplyAll();
7037
7384
  this.mounted = true;
7038
7385
 
7386
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
7387
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
7388
+ for (var ci = 0; ci < childEls.length; ci++) {
7389
+ var ch = childEls[ci]._bwComponentHandle;
7390
+ if (ch && ch !== this && !ch._parent) {
7391
+ ch._parent = this;
7392
+ this._children.push(ch);
7393
+ }
7394
+ }
7395
+
7039
7396
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
7040
7397
  if (this._hooks.mounted) {
7041
7398
  if (this._hooks.mounted.length === 2) {
@@ -7044,15 +7401,20 @@
7044
7401
  this._hooks.mounted(this);
7045
7402
  }
7046
7403
  }
7404
+
7405
+ // Invoke o.render on initial mount (if present)
7406
+ if (this.element._bw_render) {
7407
+ this.element._bw_render(this.element, this._state);
7408
+ }
7047
7409
  };
7048
7410
 
7049
7411
  /**
7050
7412
  * Prepare TACO for initial render: resolve when/each markers.
7051
7413
  * @private
7052
7414
  */
7053
- ComponentHandle.prototype._prepareTaco = function (taco) {
7054
- if (!taco || _typeof(taco) !== 'object') return;
7055
- if (Array.isArray(taco.c)) {
7415
+ _chp._prepareTaco = function (taco) {
7416
+ if (!_is(taco, 'object')) return;
7417
+ if (_isA(taco.c)) {
7056
7418
  for (var i = taco.c.length - 1; i >= 0; i--) {
7057
7419
  var child = taco.c[i];
7058
7420
  if (child && child._bwWhen) {
@@ -7093,7 +7455,7 @@
7093
7455
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
7094
7456
  var arr = bw._evaluatePath(this._state, eachExprStr);
7095
7457
  var items = [];
7096
- if (Array.isArray(arr)) {
7458
+ if (_isA(arr)) {
7097
7459
  for (var j = 0; j < arr.length; j++) {
7098
7460
  items.push(child.factory(arr[j], j));
7099
7461
  }
@@ -7107,11 +7469,11 @@
7107
7469
  c: items
7108
7470
  };
7109
7471
  }
7110
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
7472
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
7111
7473
  this._prepareTaco(taco.c[i]);
7112
7474
  }
7113
7475
  }
7114
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
7476
+ } else if (_is(taco.c, 'object') && taco.c.t) {
7115
7477
  this._prepareTaco(taco.c);
7116
7478
  }
7117
7479
  };
@@ -7120,12 +7482,12 @@
7120
7482
  * Wire action name strings (in onclick etc.) to dispatch function calls.
7121
7483
  * @private
7122
7484
  */
7123
- ComponentHandle.prototype._wireActions = function (taco) {
7124
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return;
7485
+ _chp._wireActions = function (taco) {
7486
+ if (!_is(taco, 'object') || !taco.t) return;
7125
7487
  if (taco.a) {
7126
7488
  for (var key in taco.a) {
7127
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
7128
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
7489
+ if (!_hop.call(taco.a, key)) continue;
7490
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
7129
7491
  var actionName = taco.a[key];
7130
7492
  if (actionName in this._actions) {
7131
7493
  var registeredName = this._bwId + '_' + actionName;
@@ -7139,11 +7501,11 @@
7139
7501
  }
7140
7502
  }
7141
7503
  }
7142
- if (Array.isArray(taco.c)) {
7504
+ if (_isA(taco.c)) {
7143
7505
  for (var i = 0; i < taco.c.length; i++) {
7144
7506
  this._wireActions(taco.c[i]);
7145
7507
  }
7146
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
7508
+ } else if (_is(taco.c, 'object') && taco.c.t) {
7147
7509
  this._wireActions(taco.c);
7148
7510
  }
7149
7511
  };
@@ -7152,7 +7514,7 @@
7152
7514
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
7153
7515
  * @private
7154
7516
  */
7155
- ComponentHandle.prototype._deepCloneTaco = function (taco) {
7517
+ _chp._deepCloneTaco = function (taco) {
7156
7518
  if (taco == null) return taco;
7157
7519
  // Preserve _bwWhen / _bwEach markers (contain functions)
7158
7520
  if (taco._bwWhen) {
@@ -7171,22 +7533,22 @@
7171
7533
  _refId: taco._refId
7172
7534
  };
7173
7535
  }
7174
- if (_typeof(taco) !== 'object' || !taco.t) return taco;
7536
+ if (!_is(taco, 'object') || !taco.t) return taco;
7175
7537
  var result = {
7176
7538
  t: taco.t
7177
7539
  };
7178
7540
  if (taco.a) {
7179
7541
  result.a = {};
7180
7542
  for (var k in taco.a) {
7181
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
7543
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
7182
7544
  }
7183
7545
  }
7184
7546
  if (taco.c != null) {
7185
- if (Array.isArray(taco.c)) {
7547
+ if (_isA(taco.c)) {
7186
7548
  result.c = taco.c.map(function (child) {
7187
7549
  return this._deepCloneTaco(child);
7188
7550
  }.bind(this));
7189
- } else if (_typeof(taco.c) === 'object') {
7551
+ } else if (_is(taco.c, 'object')) {
7190
7552
  result.c = this._deepCloneTaco(taco.c);
7191
7553
  } else {
7192
7554
  result.c = taco.c;
@@ -7200,31 +7562,34 @@
7200
7562
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
7201
7563
  * @private
7202
7564
  */
7203
- ComponentHandle.prototype._tacoForDOM = function (taco) {
7204
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return taco;
7565
+ _chp._tacoForDOM = function (taco) {
7566
+ if (!_is(taco, 'object') || !taco.t) return taco;
7205
7567
  var result = {
7206
7568
  t: taco.t
7207
7569
  };
7208
7570
  if (taco.a) result.a = taco.a;
7209
7571
  if (taco.c != null) {
7210
- if (Array.isArray(taco.c)) {
7572
+ if (_isA(taco.c)) {
7211
7573
  result.c = taco.c.map(function (child) {
7212
7574
  return this._tacoForDOM(child);
7213
7575
  }.bind(this));
7214
- } else if (_typeof(taco.c) === 'object' && taco.c.t) {
7576
+ } else if (_is(taco.c, 'object') && taco.c.t) {
7215
7577
  result.c = this._tacoForDOM(taco.c);
7216
7578
  } else {
7217
7579
  result.c = taco.c;
7218
7580
  }
7219
7581
  }
7220
7582
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
7583
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
7584
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t + '>. Use onclick attribute or bw.component() for child interactivity.');
7585
+ }
7221
7586
  return result;
7222
7587
  };
7223
7588
 
7224
7589
  /**
7225
7590
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
7226
7591
  */
7227
- ComponentHandle.prototype.unmount = function () {
7592
+ _chp.unmount = function () {
7228
7593
  if (!this.mounted) return;
7229
7594
 
7230
7595
  // unmount hook
@@ -7258,11 +7623,22 @@
7258
7623
  /**
7259
7624
  * Destroy: unmount + clear state + unregister actions.
7260
7625
  */
7261
- ComponentHandle.prototype.destroy = function () {
7626
+ _chp.destroy = function () {
7262
7627
  // willDestroy hook
7263
7628
  if (this._hooks.willDestroy) {
7264
7629
  this._hooks.willDestroy(this);
7265
7630
  }
7631
+
7632
+ // Cascade destroy to children depth-first (Bug #5)
7633
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
7634
+ this._children[ci].destroy();
7635
+ }
7636
+ this._children = [];
7637
+ if (this._parent) {
7638
+ var idx = this._parent._children.indexOf(this);
7639
+ if (idx >= 0) this._parent._children.splice(idx, 1);
7640
+ this._parent = null;
7641
+ }
7266
7642
  this.unmount();
7267
7643
 
7268
7644
  // Unregister actions from function registry
@@ -7289,12 +7665,37 @@
7289
7665
  * Flush dirty state: resolve changed bindings and apply to DOM.
7290
7666
  * @private
7291
7667
  */
7292
- ComponentHandle.prototype._flush = function () {
7668
+ _chp._flush = function () {
7293
7669
  this._scheduled = false;
7294
- var changedKeys = Object.keys(this._dirtyKeys);
7670
+ var changedKeys = _keys(this._dirtyKeys);
7295
7671
  this._dirtyKeys = {};
7296
7672
  if (changedKeys.length === 0 || !this.mounted) return;
7297
7673
 
7674
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
7675
+ // rebuild the TACO from the factory with merged state (Bug #6)
7676
+ if (this._factory) {
7677
+ var rebuildNeeded = false;
7678
+ for (var fi = 0; fi < changedKeys.length; fi++) {
7679
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
7680
+ rebuildNeeded = true;
7681
+ break;
7682
+ }
7683
+ }
7684
+ if (rebuildNeeded) {
7685
+ var merged = {};
7686
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
7687
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
7688
+ this._factory.props = merged;
7689
+ var newTaco = bw.make(this._factory.type, merged);
7690
+ newTaco._bwFactory = this._factory;
7691
+ this.taco = newTaco;
7692
+ this._originalTaco = this._deepCloneTaco(newTaco);
7693
+ this._render();
7694
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
7695
+ return;
7696
+ }
7697
+ }
7698
+
7298
7699
  // willUpdate hook
7299
7700
  if (this._hooks.willUpdate) {
7300
7701
  this._hooks.willUpdate(this, changedKeys);
@@ -7332,7 +7733,7 @@
7332
7733
  * Returns list of patches to apply.
7333
7734
  * @private
7334
7735
  */
7335
- ComponentHandle.prototype._resolveBindings = function (changedKeys) {
7736
+ _chp._resolveBindings = function (changedKeys) {
7336
7737
  var patches = [];
7337
7738
  for (var i = 0; i < this._bindings.length; i++) {
7338
7739
  var b = this._bindings[i];
@@ -7368,11 +7769,14 @@
7368
7769
  * Apply patches to DOM.
7369
7770
  * @private
7370
7771
  */
7371
- ComponentHandle.prototype._applyPatches = function (patches) {
7772
+ _chp._applyPatches = function (patches) {
7372
7773
  for (var i = 0; i < patches.length; i++) {
7373
7774
  var p = patches[i];
7374
7775
  var el = this._bw_refs[p.refId];
7375
- if (!el) continue;
7776
+ if (!el) {
7777
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
7778
+ continue;
7779
+ }
7376
7780
  if (p.type === 'content') {
7377
7781
  el.textContent = p.value;
7378
7782
  } else if (p.type === 'attribute') {
@@ -7389,7 +7793,7 @@
7389
7793
  * Resolve all bindings and apply (used for initial render).
7390
7794
  * @private
7391
7795
  */
7392
- ComponentHandle.prototype._resolveAndApplyAll = function () {
7796
+ _chp._resolveAndApplyAll = function () {
7393
7797
  var patches = [];
7394
7798
  for (var i = 0; i < this._bindings.length; i++) {
7395
7799
  var b = this._bindings[i];
@@ -7411,7 +7815,7 @@
7411
7815
  * Full re-render for structural changes (when/each branch switches).
7412
7816
  * @private
7413
7817
  */
7414
- ComponentHandle.prototype._render = function () {
7818
+ _chp._render = function () {
7415
7819
  if (!this.element || !this.element.parentNode) return;
7416
7820
  var parent = this.element.parentNode;
7417
7821
  var nextSibling = this.element.nextSibling;
@@ -7450,7 +7854,7 @@
7450
7854
  * @param {string} event - Event name (e.g., 'click')
7451
7855
  * @param {Function} handler - Event handler
7452
7856
  */
7453
- ComponentHandle.prototype.on = function (event, handler) {
7857
+ _chp.on = function (event, handler) {
7454
7858
  if (this.element) {
7455
7859
  this.element.addEventListener(event, handler);
7456
7860
  }
@@ -7465,7 +7869,7 @@
7465
7869
  * @param {string} event - Event name
7466
7870
  * @param {Function} handler - Handler to remove
7467
7871
  */
7468
- ComponentHandle.prototype.off = function (event, handler) {
7872
+ _chp.off = function (event, handler) {
7469
7873
  if (this.element) {
7470
7874
  this.element.removeEventListener(event, handler);
7471
7875
  }
@@ -7480,7 +7884,7 @@
7480
7884
  * @param {Function} handler - Handler function
7481
7885
  * @returns {Function} Unsubscribe function
7482
7886
  */
7483
- ComponentHandle.prototype.sub = function (topic, handler) {
7887
+ _chp.sub = function (topic, handler) {
7484
7888
  var unsub = bw.sub(topic, handler);
7485
7889
  this._subs.push(unsub);
7486
7890
  return unsub;
@@ -7491,10 +7895,10 @@
7491
7895
  * @param {string} name - Action name
7492
7896
  * @param {...*} args - Arguments passed after comp
7493
7897
  */
7494
- ComponentHandle.prototype.action = function (name) {
7898
+ _chp.action = function (name) {
7495
7899
  var fn = this._actions[name];
7496
7900
  if (!fn) {
7497
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
7901
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
7498
7902
  return;
7499
7903
  }
7500
7904
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -7506,7 +7910,7 @@
7506
7910
  * @param {string} sel - CSS selector
7507
7911
  * @returns {Element|null}
7508
7912
  */
7509
- ComponentHandle.prototype.select = function (sel) {
7913
+ _chp.select = function (sel) {
7510
7914
  return this.element ? this.element.querySelector(sel) : null;
7511
7915
  };
7512
7916
 
@@ -7515,7 +7919,7 @@
7515
7919
  * @param {string} sel - CSS selector
7516
7920
  * @returns {Element[]}
7517
7921
  */
7518
- ComponentHandle.prototype.selectAll = function (sel) {
7922
+ _chp.selectAll = function (sel) {
7519
7923
  if (!this.element) return [];
7520
7924
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
7521
7925
  };
@@ -7526,7 +7930,7 @@
7526
7930
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
7527
7931
  * @returns {ComponentHandle} this (for chaining)
7528
7932
  */
7529
- ComponentHandle.prototype.userTag = function (tag) {
7933
+ _chp.userTag = function (tag) {
7530
7934
  this._userTag = tag;
7531
7935
  if (this.element) {
7532
7936
  this.element.classList.add(tag);
@@ -7635,14 +8039,384 @@
7635
8039
  }
7636
8040
  if (!el || !el._bwComponentHandle) return false;
7637
8041
  var comp = el._bwComponentHandle;
7638
- if (typeof comp[action] !== 'function') {
7639
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
8042
+ if (!_is(comp[action], 'function')) {
8043
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
7640
8044
  return false;
7641
8045
  }
7642
8046
  comp[action](data);
7643
8047
  return true;
7644
8048
  };
7645
8049
 
8050
+ // ===================================================================================
8051
+ // bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
8052
+ // ===================================================================================
8053
+
8054
+ /**
8055
+ * Registry of named functions sent via register messages.
8056
+ * Populated by clientApply({ type: 'register', name, body }).
8057
+ * Invoked by clientApply({ type: 'call', name, args }).
8058
+ * @private
8059
+ */
8060
+ bw._clientFunctions = {};
8061
+
8062
+ /**
8063
+ * Whether exec messages are allowed. Set by clientConnect opts.allowExec.
8064
+ * Default false — exec messages are rejected unless explicitly opted in.
8065
+ * @private
8066
+ */
8067
+ bw._allowExec = false;
8068
+
8069
+ /**
8070
+ * Built-in client functions available via call() without registration.
8071
+ * @private
8072
+ */
8073
+ bw._builtinClientFunctions = {
8074
+ scrollTo: function scrollTo(selector) {
8075
+ var el = bw._el(selector);
8076
+ if (el) el.scrollTop = el.scrollHeight;
8077
+ },
8078
+ focus: function focus(selector) {
8079
+ var el = bw._el(selector);
8080
+ if (el && _is(el.focus, 'function')) el.focus();
8081
+ },
8082
+ download: function download(filename, content, mimeType) {
8083
+ if (typeof document === 'undefined') return;
8084
+ var blob = new Blob([content], {
8085
+ type: mimeType || 'text/plain'
8086
+ });
8087
+ var a = document.createElement('a');
8088
+ a.href = URL.createObjectURL(blob);
8089
+ a.download = filename;
8090
+ a.click();
8091
+ URL.revokeObjectURL(a.href);
8092
+ },
8093
+ clipboard: function clipboard(text) {
8094
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
8095
+ navigator.clipboard.writeText(text);
8096
+ }
8097
+ },
8098
+ redirect: function redirect(url) {
8099
+ if (typeof window !== 'undefined') window.location.href = url;
8100
+ },
8101
+ log: function log() {
8102
+ console.log.apply(console, arguments);
8103
+ }
8104
+ };
8105
+
8106
+ /**
8107
+ * Parse a bwserve protocol message string, supporting both strict JSON
8108
+ * and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
8109
+ *
8110
+ * The r-prefix format is designed for C/C++ string literals where
8111
+ * double-quote escaping is painful. The parser is a state machine
8112
+ * that walks character by character — not a regex replace.
8113
+ *
8114
+ * Escaping: apostrophes inside single-quoted values must be escaped
8115
+ * with backslash: r{'name':'Barry\'s room'}
8116
+ *
8117
+ * @param {string} str - JSON or r-prefixed relaxed JSON string
8118
+ * @returns {Object} Parsed message object
8119
+ * @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
8120
+ * @category Server
8121
+ */
8122
+ bw.clientParse = function (str) {
8123
+ str = (str || '').trim();
8124
+ if (str.charAt(0) !== 'r') return JSON.parse(str);
8125
+ str = str.slice(1);
8126
+ var out = [];
8127
+ var i = 0;
8128
+ var len = str.length;
8129
+ while (i < len) {
8130
+ var ch = str[i];
8131
+ if (ch === "'") {
8132
+ // Single-quoted string → emit as double-quoted
8133
+ out.push('"');
8134
+ i++;
8135
+ while (i < len) {
8136
+ var c = str[i];
8137
+ if (c === '\\' && i + 1 < len) {
8138
+ var next = str[i + 1];
8139
+ if (next === "'") {
8140
+ out.push("'"); // \' in input → ' in output
8141
+ } else {
8142
+ out.push('\\');
8143
+ out.push(next);
8144
+ }
8145
+ i += 2;
8146
+ } else if (c === '"') {
8147
+ out.push('\\"');
8148
+ i++;
8149
+ } else if (c === "'") {
8150
+ break;
8151
+ } else {
8152
+ out.push(c);
8153
+ i++;
8154
+ }
8155
+ }
8156
+ out.push('"');
8157
+ i++; // skip closing '
8158
+ } else if (ch === '"') {
8159
+ // Double-quoted string — pass through verbatim
8160
+ out.push(ch);
8161
+ i++;
8162
+ while (i < len) {
8163
+ var c2 = str[i];
8164
+ if (c2 === '\\' && i + 1 < len) {
8165
+ out.push(c2);
8166
+ out.push(str[i + 1]);
8167
+ i += 2;
8168
+ } else {
8169
+ out.push(c2);
8170
+ i++;
8171
+ if (c2 === '"') break;
8172
+ }
8173
+ }
8174
+ } else if (ch === ',') {
8175
+ // Trailing comma check: skip comma if next non-whitespace is } or ]
8176
+ var j = i + 1;
8177
+ while (j < len && (str[j] === ' ' || str[j] === '\t' || str[j] === '\n' || str[j] === '\r')) j++;
8178
+ if (j < len && (str[j] === '}' || str[j] === ']')) {
8179
+ i++; // skip trailing comma
8180
+ } else {
8181
+ out.push(ch);
8182
+ i++;
8183
+ }
8184
+ } else {
8185
+ out.push(ch);
8186
+ i++;
8187
+ }
8188
+ }
8189
+ return JSON.parse(out.join(''));
8190
+ };
8191
+
8192
+ /**
8193
+ * Apply a bwserve protocol message to the DOM.
8194
+ *
8195
+ * Dispatches one of 9 message types:
8196
+ * replace — bw.DOM(target, node)
8197
+ * append — target.appendChild(bw.createDOM(node))
8198
+ * remove — bw.cleanup(target); target.remove()
8199
+ * patch — bw.patch(target, content, attr)
8200
+ * batch — iterate ops, call clientApply for each
8201
+ * message — bw.message(target, action, data)
8202
+ * register — store a named function for later call()
8203
+ * call — invoke a registered or built-in function
8204
+ * exec — execute arbitrary JS (requires allowExec)
8205
+ *
8206
+ * Target resolution:
8207
+ * Starts with '#' or '.' → CSS selector (querySelector)
8208
+ * Otherwise → getElementById, then bw._el fallback
8209
+ *
8210
+ * @param {Object} msg - Protocol message
8211
+ * @returns {boolean} true if the message was applied successfully
8212
+ * @category Server
8213
+ */
8214
+ bw.clientApply = function (msg) {
8215
+ if (!msg || !msg.type) return false;
8216
+ var type = msg.type;
8217
+ var target = msg.target;
8218
+ if (type === 'replace') {
8219
+ var el = bw._el(target);
8220
+ if (!el) return false;
8221
+ bw.DOM(el, msg.node);
8222
+ return true;
8223
+ } else if (type === 'patch') {
8224
+ var patched = bw.patch(target, msg.content, msg.attr);
8225
+ return patched !== null;
8226
+ } else if (type === 'append') {
8227
+ var parent = bw._el(target);
8228
+ if (!parent) return false;
8229
+ var child = bw.createDOM(msg.node);
8230
+ parent.appendChild(child);
8231
+ return true;
8232
+ } else if (type === 'remove') {
8233
+ var toRemove = bw._el(target);
8234
+ if (!toRemove) return false;
8235
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
8236
+ toRemove.remove();
8237
+ return true;
8238
+ } else if (type === 'batch') {
8239
+ if (!_isA(msg.ops)) return false;
8240
+ var allOk = true;
8241
+ msg.ops.forEach(function (op) {
8242
+ if (!bw.clientApply(op)) allOk = false;
8243
+ });
8244
+ return allOk;
8245
+ } else if (type === 'message') {
8246
+ return bw.message(msg.target, msg.action, msg.data);
8247
+ } else if (type === 'register') {
8248
+ if (!msg.name || !msg.body) return false;
8249
+ try {
8250
+ bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
8251
+ return true;
8252
+ } catch (e) {
8253
+ _ce('[bw] register error:', msg.name, e);
8254
+ return false;
8255
+ }
8256
+ } else if (type === 'call') {
8257
+ if (!msg.name) return false;
8258
+ var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
8259
+ if (!_is(fn, 'function')) return false;
8260
+ try {
8261
+ var args = _isA(msg.args) ? msg.args : [];
8262
+ fn.apply(null, args);
8263
+ return true;
8264
+ } catch (e) {
8265
+ _ce('[bw] call error:', msg.name, e);
8266
+ return false;
8267
+ }
8268
+ } else if (type === 'exec') {
8269
+ if (!bw._allowExec) {
8270
+ _cw('[bw] exec rejected: allowExec is not enabled');
8271
+ return false;
8272
+ }
8273
+ if (!msg.code) return false;
8274
+ try {
8275
+ new Function(msg.code)();
8276
+ return true;
8277
+ } catch (e) {
8278
+ _ce('[bw] exec error:', e);
8279
+ return false;
8280
+ }
8281
+ }
8282
+ return false;
8283
+ };
8284
+
8285
+ /**
8286
+ * Connect to a bwserve SSE endpoint and apply protocol messages automatically.
8287
+ *
8288
+ * Returns a connection object with sendAction(), on(), and close() methods.
8289
+ *
8290
+ * @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
8291
+ * @param {Object} [opts] - Connection options
8292
+ * @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
8293
+ * @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
8294
+ * @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
8295
+ * @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
8296
+ * @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
8297
+ * @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
8298
+ * @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
8299
+ * @returns {Object} Connection object { sendAction, on, close, status }
8300
+ * @category Server
8301
+ */
8302
+ bw.clientConnect = function (url, opts) {
8303
+ opts = opts || {};
8304
+ var transport = opts.transport || 'sse';
8305
+ var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
8306
+ var reconnect = opts.reconnect !== false;
8307
+ var onStatus = opts.onStatus || function () {};
8308
+ var onMessage = opts.onMessage || null;
8309
+ var handlers = {};
8310
+ // Set the global allowExec flag from connection options
8311
+ bw._allowExec = !!opts.allowExec;
8312
+ var conn = {
8313
+ status: 'connecting',
8314
+ _es: null,
8315
+ _pollTimer: null
8316
+ };
8317
+ function setStatus(s) {
8318
+ conn.status = s;
8319
+ onStatus(s);
8320
+ }
8321
+ function handleMessage(data) {
8322
+ try {
8323
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
8324
+ if (onMessage) onMessage(msg);
8325
+ if (handlers.message) handlers.message(msg);
8326
+ bw.clientApply(msg);
8327
+ } catch (e) {
8328
+ if (handlers.error) handlers.error(e);
8329
+ }
8330
+ }
8331
+ if (transport === 'sse' && typeof EventSource !== 'undefined') {
8332
+ setStatus('connecting');
8333
+ var es = new EventSource(url);
8334
+ conn._es = es;
8335
+ es.onopen = function () {
8336
+ setStatus('connected');
8337
+ if (handlers.open) handlers.open();
8338
+ };
8339
+ es.onmessage = function (e) {
8340
+ handleMessage(e.data);
8341
+ };
8342
+ es.onerror = function () {
8343
+ if (conn.status === 'connected') {
8344
+ setStatus('disconnected');
8345
+ }
8346
+ if (handlers.error) handlers.error(new Error('SSE connection error'));
8347
+ if (!reconnect) {
8348
+ es.close();
8349
+ }
8350
+ // EventSource auto-reconnects by default when reconnect=true
8351
+ };
8352
+ } else if (transport === 'poll') {
8353
+ var interval = opts.interval || 2000;
8354
+ setStatus('connected');
8355
+ conn._pollTimer = setInterval(function () {
8356
+ fetch(url).then(function (r) {
8357
+ return r.json();
8358
+ }).then(function (msgs) {
8359
+ if (_isA(msgs)) {
8360
+ msgs.forEach(handleMessage);
8361
+ } else if (msgs && msgs.type) {
8362
+ handleMessage(msgs);
8363
+ }
8364
+ })["catch"](function (e) {
8365
+ if (handlers.error) handlers.error(e);
8366
+ });
8367
+ }, interval);
8368
+ }
8369
+
8370
+ /**
8371
+ * Send an action to the server via POST.
8372
+ * @param {string} action - Action name
8373
+ * @param {Object} [data] - Action payload
8374
+ */
8375
+ conn.sendAction = function (action, data) {
8376
+ var body = JSON.stringify({
8377
+ type: 'action',
8378
+ action: action,
8379
+ data: data || {}
8380
+ });
8381
+ fetch(actionUrl, {
8382
+ method: 'POST',
8383
+ headers: {
8384
+ 'Content-Type': 'application/json'
8385
+ },
8386
+ body: body
8387
+ })["catch"](function (e) {
8388
+ if (handlers.error) handlers.error(e);
8389
+ });
8390
+ };
8391
+
8392
+ /**
8393
+ * Register an event handler.
8394
+ * @param {string} event - 'open'|'message'|'error'|'close'
8395
+ * @param {Function} handler
8396
+ */
8397
+ conn.on = function (event, handler) {
8398
+ handlers[event] = handler;
8399
+ return conn;
8400
+ };
8401
+
8402
+ /**
8403
+ * Close the connection.
8404
+ */
8405
+ conn.close = function () {
8406
+ if (conn._es) {
8407
+ conn._es.close();
8408
+ conn._es = null;
8409
+ }
8410
+ if (conn._pollTimer) {
8411
+ clearInterval(conn._pollTimer);
8412
+ conn._pollTimer = null;
8413
+ }
8414
+ setStatus('disconnected');
8415
+ if (handlers.close) handlers.close();
8416
+ };
8417
+ return conn;
8418
+ };
8419
+
7646
8420
  // ===================================================================================
7647
8421
  // bw.inspect() — Debug utility
7648
8422
  // ===================================================================================
@@ -7669,20 +8443,20 @@
7669
8443
  el = target.element;
7670
8444
  comp = target;
7671
8445
  } else {
7672
- if (typeof target === 'string') {
8446
+ if (_is(target, 'string')) {
7673
8447
  el = bw.$(target)[0];
7674
8448
  }
7675
8449
  if (!el) {
7676
- console.warn('bw.inspect: element not found');
8450
+ _cw('bw.inspect: element not found');
7677
8451
  return null;
7678
8452
  }
7679
8453
  comp = el._bwComponentHandle;
7680
8454
  }
7681
8455
  if (!comp) {
7682
- console.log('bw.inspect: no ComponentHandle on this element');
7683
- console.log(' Tag:', el.tagName);
7684
- console.log(' Classes:', el.className);
7685
- console.log(' _bw_state:', el._bw_state || '(none)');
8456
+ _cl('bw.inspect: no ComponentHandle on this element');
8457
+ _cl(' Tag:', el.tagName);
8458
+ _cl(' Classes:', el.className);
8459
+ _cl(' _bw_state:', el._bw_state || '(none)');
7686
8460
  return null;
7687
8461
  }
7688
8462
  var deps = comp._bindings.reduce(function (s, b) {
@@ -7691,13 +8465,13 @@
7691
8465
  return a.indexOf(v) === i;
7692
8466
  });
7693
8467
  console.group('Component: ' + comp._bwId);
7694
- console.log('State:', comp._state);
7695
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
7696
- console.log('Methods:', Object.keys(comp._methods));
7697
- console.log('Actions:', Object.keys(comp._actions));
7698
- console.log('User tag:', comp._userTag || '(none)');
7699
- console.log('Mounted:', comp.mounted);
7700
- console.log('Element:', comp.element);
8468
+ _cl('State:', comp._state);
8469
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
8470
+ _cl('Methods:', _keys(comp._methods));
8471
+ _cl('Actions:', _keys(comp._actions));
8472
+ _cl('User tag:', comp._userTag || '(none)');
8473
+ _cl('Mounted:', comp.mounted);
8474
+ _cl('Element:', comp.element);
7701
8475
  console.groupEnd();
7702
8476
  return comp;
7703
8477
  };
@@ -7720,8 +8494,8 @@
7720
8494
  // Pre-extract all binding expressions
7721
8495
  var precompiled = [];
7722
8496
  function walkExpressions(node) {
7723
- if (!node || _typeof(node) !== 'object') return;
7724
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
8497
+ if (!_is(node, 'object')) return;
8498
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
7725
8499
  var parsed = bw._parseBindings(node.c);
7726
8500
  for (var i = 0; i < parsed.length; i++) {
7727
8501
  try {
@@ -7741,9 +8515,9 @@
7741
8515
  }
7742
8516
  if (node.a) {
7743
8517
  for (var key in node.a) {
7744
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
8518
+ if (_hop.call(node.a, key)) {
7745
8519
  var v = node.a[key];
7746
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
8520
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
7747
8521
  var parsed2 = bw._parseBindings(v);
7748
8522
  for (var j = 0; j < parsed2.length; j++) {
7749
8523
  try {
@@ -7764,9 +8538,9 @@
7764
8538
  }
7765
8539
  }
7766
8540
  }
7767
- if (Array.isArray(node.c)) {
8541
+ if (_isA(node.c)) {
7768
8542
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
7769
- } else if (node.c && _typeof(node.c) === 'object' && node.c.t) {
8543
+ } else if (_is(node.c, 'object') && node.c.t) {
7770
8544
  walkExpressions(node.c);
7771
8545
  }
7772
8546
  }
@@ -7777,7 +8551,7 @@
7777
8551
  handle._precompiledBindings = precompiled;
7778
8552
  if (initialState) {
7779
8553
  for (var k in initialState) {
7780
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
8554
+ if (_hop.call(initialState, k)) {
7781
8555
  handle._state[k] = initialState[k];
7782
8556
  }
7783
8557
  }
@@ -7811,21 +8585,21 @@
7811
8585
  minify = _options$minify === void 0 ? false : _options$minify,
7812
8586
  _options$pretty = options.pretty,
7813
8587
  pretty = _options$pretty === void 0 ? !minify : _options$pretty;
7814
- if (typeof rules === 'string') return rules;
8588
+ if (_is(rules, 'string')) return rules;
7815
8589
  var css = '';
7816
8590
  var indent = pretty ? ' ' : '';
7817
8591
  var newline = pretty ? '\n' : '';
7818
8592
  var space = pretty ? ' ' : '';
7819
- if (Array.isArray(rules)) {
8593
+ if (_isA(rules)) {
7820
8594
  css = rules.map(function (rule) {
7821
8595
  return bw.css(rule, options);
7822
8596
  }).join(newline);
7823
- } else if (_typeof(rules) === 'object') {
8597
+ } else if (_is(rules, 'object')) {
7824
8598
  Object.entries(rules).forEach(function (_ref5) {
7825
8599
  var _ref6 = _slicedToArray(_ref5, 2),
7826
8600
  selector = _ref6[0],
7827
8601
  styles = _ref6[1];
7828
- if (_typeof(styles) === 'object' && !Array.isArray(styles)) {
8602
+ if (_is(styles, 'object')) {
7829
8603
  // Handle @media, @keyframes, @supports — recurse into nested block
7830
8604
  if (selector.charAt(0) === '@') {
7831
8605
  var inner = bw.css(styles, options);
@@ -7879,7 +8653,7 @@
7879
8653
  bw.injectCSS = function (css) {
7880
8654
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
7881
8655
  if (!bw._isBrowser) {
7882
- console.warn('bw.injectCSS requires a DOM environment');
8656
+ _cw('bw.injectCSS requires a DOM environment');
7883
8657
  return null;
7884
8658
  }
7885
8659
  var _options$id = options.id,
@@ -7897,7 +8671,7 @@
7897
8671
  }
7898
8672
 
7899
8673
  // Convert CSS if needed
7900
- var cssStr = typeof css === 'string' ? css : bw.css(css, options);
8674
+ var cssStr = _is(css, 'string') ? css : bw.css(css, options);
7901
8675
 
7902
8676
  // Set or append CSS
7903
8677
  if (append && styleEl.textContent) {
@@ -7926,7 +8700,7 @@
7926
8700
  var result = {};
7927
8701
  for (var i = 0; i < arguments.length; i++) {
7928
8702
  var arg = arguments[i];
7929
- if (arg && _typeof(arg) === 'object') Object.assign(result, arg);
8703
+ if (_is(arg, 'object')) Object.assign(result, arg);
7930
8704
  }
7931
8705
  return result;
7932
8706
  };
@@ -8171,7 +8945,7 @@
8171
8945
  xl: '1200px'
8172
8946
  };
8173
8947
  var parts = [];
8174
- Object.keys(breakpoints).forEach(function (key) {
8948
+ _keys(breakpoints).forEach(function (key) {
8175
8949
  var rules = {};
8176
8950
  if (key === 'base') {
8177
8951
  rules[selector] = breakpoints[key];
@@ -8243,18 +9017,18 @@
8243
9017
  if (!selector) return [];
8244
9018
 
8245
9019
  // Already an array
8246
- if (Array.isArray(selector)) return selector;
9020
+ if (_isA(selector)) return selector;
8247
9021
 
8248
9022
  // Single element
8249
9023
  if (selector.nodeType) return [selector];
8250
9024
 
8251
9025
  // NodeList or HTMLCollection
8252
- if (selector.length !== undefined && typeof selector !== 'string') {
9026
+ if (selector.length !== undefined && !_is(selector, 'string')) {
8253
9027
  return Array.from(selector);
8254
9028
  }
8255
9029
 
8256
9030
  // CSS selector string
8257
- if (typeof selector === 'string') {
9031
+ if (_is(selector, 'string')) {
8258
9032
  return Array.from(document.querySelectorAll(selector));
8259
9033
  }
8260
9034
  return [];
@@ -8772,7 +9546,7 @@
8772
9546
  cls = cls.trim();
8773
9547
 
8774
9548
  // Auto-detect columns if not provided
8775
- var cols = columns || (data.length > 0 ? Object.keys(data[0]).map(function (key) {
9549
+ var cols = columns || (data.length > 0 ? _keys(data[0]).map(function (key) {
8776
9550
  return {
8777
9551
  key: key,
8778
9552
  label: key
@@ -8791,7 +9565,7 @@
8791
9565
  var bVal = b[currentSortColumn];
8792
9566
 
8793
9567
  // Handle different types
8794
- if (typeof aVal === 'number' && typeof bVal === 'number') {
9568
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
8795
9569
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
8796
9570
  }
8797
9571
 
@@ -8915,7 +9689,7 @@
8915
9689
  headerRow = _config$headerRow === void 0 ? true : _config$headerRow,
8916
9690
  columns = config.columns,
8917
9691
  rest = _objectWithoutProperties(config, _excluded);
8918
- if (!Array.isArray(data) || data.length === 0) {
9692
+ if (!_isA(data) || data.length === 0) {
8919
9693
  return bw.makeTable(_objectSpread2({
8920
9694
  data: [],
8921
9695
  columns: columns || []
@@ -9012,7 +9786,7 @@
9012
9786
  showLabels = _config$showLabels === void 0 ? true : _config$showLabels,
9013
9787
  _config$className2 = config.className,
9014
9788
  className = _config$className2 === void 0 ? '' : _config$className2;
9015
- if (!Array.isArray(data) || data.length === 0) {
9789
+ if (!_isA(data) || data.length === 0) {
9016
9790
  return {
9017
9791
  t: 'div',
9018
9792
  a: {
@@ -9195,7 +9969,7 @@
9195
9969
  bw.render = function (element, position, taco) {
9196
9970
  var _taco$o4, _taco$o5, _taco$o6;
9197
9971
  // Get target element
9198
- var targetEl = typeof element === 'string' ? document.querySelector(element) : element;
9972
+ var targetEl = _is(element, 'string') ? document.querySelector(element) : element;
9199
9973
  if (!targetEl) {
9200
9974
  return {
9201
9975
  object_type: 'error',
@@ -9333,7 +10107,7 @@
9333
10107
  setContent: function setContent(content) {
9334
10108
  this._taco.c = content;
9335
10109
  if (this.element) {
9336
- if (typeof content === 'string') {
10110
+ if (_is(content, 'string')) {
9337
10111
  this.element.textContent = content;
9338
10112
  } else {
9339
10113
  // Re-render for complex content