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 v2.0.15 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench 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
  }
@@ -9038,7 +9081,14 @@
9038
9081
  function make(type, props) {
9039
9082
  var def = BCCL[type];
9040
9083
  if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
9041
- return def.make(props || {});
9084
+ var taco = def.make(props || {});
9085
+ if (taco && _typeof(taco) === 'object') {
9086
+ taco._bwFactory = {
9087
+ type: type,
9088
+ props: props || {}
9089
+ };
9090
+ }
9091
+ return taco;
9042
9092
  }
9043
9093
 
9044
9094
  var components = /*#__PURE__*/Object.freeze({
@@ -9151,7 +9201,7 @@
9151
9201
  __monkey_patch_is_nodejs__: {
9152
9202
  _value: 'ignore',
9153
9203
  set: function set(x) {
9154
- this._value = typeof x === 'boolean' ? x : 'ignore';
9204
+ this._value = _is(x, 'boolean') ? x : 'ignore';
9155
9205
  },
9156
9206
  get: function get() {
9157
9207
  return this._value;
@@ -9199,6 +9249,76 @@
9199
9249
  configurable: true
9200
9250
  });
9201
9251
 
9252
+ // ── Internal aliases ─────────────────────────────────────────────────────
9253
+ // Short names for frequently-used builtins and internal methods.
9254
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
9255
+ //
9256
+ // Why: Terser can't shorten global property chains (console.warn,
9257
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
9258
+ // because it can't prove they're side-effect-free. We can, so we alias
9259
+ // them here. Each alias saves bytes in the minified output, and the short
9260
+ // names also reduce visual noise in the hot paths (binding pipeline,
9261
+ // createDOM, etc.).
9262
+ //
9263
+ // Alias Target Sites
9264
+ // ───────── ────────────────────────────────────── ─────
9265
+ // _hop Object.prototype.hasOwnProperty 15
9266
+ // _isA Array.isArray 25
9267
+ // _keys Object.keys 7
9268
+ // _to bw.typeOf (type string) 26
9269
+ // _is type check boolean: _is(x,'string') ~50
9270
+ // _cw console.warn 8
9271
+ // _cl console.log 11
9272
+ // _ce console.error 4
9273
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
9274
+ //
9275
+ // Note: document.createElement etc. are NOT aliased because they require
9276
+ // `this === document` and .bind() would add overhead on every call.
9277
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
9278
+ // patching of console.warn/log/error continues to work.
9279
+ //
9280
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
9281
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
9282
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
9283
+ //
9284
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
9285
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
9286
+ // returns trueVal if _to(x)===type, else falseVal.
9287
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
9288
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
9289
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
9290
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
9291
+ // Uncomment if pattern frequency justifies them:
9292
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
9293
+ // 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); };
9294
+ // ─────────────────────────────────────────────────────────────────────────
9295
+ var _hop = Object.prototype.hasOwnProperty;
9296
+ var _isA = Array.isArray;
9297
+ var _keys = Object.keys;
9298
+ var _to = typeOf; // imported from bitwrench-utils.js
9299
+ var _is = function _is(x, t) {
9300
+ var r = _to(x);
9301
+ return r === t || r.toLowerCase() === t;
9302
+ };
9303
+ // Console aliases use thin wrappers (not direct references) so that test
9304
+ // code can monkey-patch console.warn/log/error and the patches take effect.
9305
+ var _cw = function _cw() {
9306
+ console.warn.apply(console, arguments);
9307
+ };
9308
+ var _cl = function _cl() {
9309
+ console.log.apply(console, arguments);
9310
+ };
9311
+ var _ce = function _ce() {
9312
+ console.error.apply(console, arguments);
9313
+ };
9314
+
9315
+ /**
9316
+ * Debug flag. When true, emits console.warn for silent binding failures
9317
+ * (missing paths, null refs, auto-created intermediate objects).
9318
+ * @type {boolean}
9319
+ */
9320
+ bw.debug = false;
9321
+
9202
9322
  /**
9203
9323
  * Lazy-resolve Node.js `fs` module.
9204
9324
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -9348,7 +9468,7 @@
9348
9468
  */
9349
9469
  bw._el = function (id) {
9350
9470
  // Pass-through for DOM elements
9351
- if (typeof id !== 'string') return id || null;
9471
+ if (!_is(id, 'string')) return id || null;
9352
9472
  if (!id) return null;
9353
9473
  if (!bw._isBrowser) return null;
9354
9474
 
@@ -9443,7 +9563,7 @@
9443
9563
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
9444
9564
  */
9445
9565
  bw.escapeHTML = function (str) {
9446
- if (typeof str !== 'string') return '';
9566
+ if (!_is(str, 'string')) return '';
9447
9567
  var escapeMap = {
9448
9568
  '&': '&amp;',
9449
9569
  '<': '&lt;',
@@ -9520,7 +9640,7 @@
9520
9640
  }
9521
9641
 
9522
9642
  // Handle arrays of TACOs
9523
- if (Array.isArray(taco)) {
9643
+ if (_isA(taco)) {
9524
9644
  return taco.map(function (t) {
9525
9645
  return bw.html(t, options);
9526
9646
  }).join('');
@@ -9543,17 +9663,17 @@
9543
9663
  if (taco && taco._bwEach && options.state) {
9544
9664
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
9545
9665
  var arr = bw._evaluatePath(options.state, eachExpr);
9546
- if (!Array.isArray(arr)) return '';
9666
+ if (!_isA(arr)) return '';
9547
9667
  return arr.map(function (item, idx) {
9548
9668
  return bw.html(taco.factory(item, idx), options);
9549
9669
  }).join('');
9550
9670
  }
9551
9671
 
9552
9672
  // Handle primitives and non-TACO objects
9553
- if (_typeof(taco) !== 'object' || !taco.t) {
9673
+ if (!_is(taco, 'object') || !taco.t) {
9554
9674
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
9555
9675
  // Resolve template bindings if state provided
9556
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
9676
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
9557
9677
  str = bw._resolveTemplate(str, options.state, !!options.compile);
9558
9678
  }
9559
9679
  return str;
@@ -9578,9 +9698,17 @@
9578
9698
  // Skip null, undefined, false
9579
9699
  if (value == null || value === false) continue;
9580
9700
 
9581
- // Skip event handlers (they're for DOM only)
9582
- if (key.startsWith('on')) continue;
9583
- if (key === 'style' && _typeof(value) === 'object') {
9701
+ // Serialize event handlers via funcRegister
9702
+ if (key.startsWith('on')) {
9703
+ if (_is(value, 'function')) {
9704
+ var fnId = bw.funcRegister(value);
9705
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
9706
+ } else if (_is(value, 'string')) {
9707
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
9708
+ }
9709
+ continue;
9710
+ }
9711
+ if (key === 'style' && _is(value, 'object')) {
9584
9712
  // Convert style object to string
9585
9713
  var styleStr = Object.entries(value).filter(function (_ref) {
9586
9714
  var _ref2 = _slicedToArray(_ref, 2),
@@ -9597,7 +9725,7 @@
9597
9725
  }
9598
9726
  } else if (key === 'class') {
9599
9727
  // Handle class as array or string
9600
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
9728
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
9601
9729
  if (classStr) {
9602
9730
  attrStr += " class=\"".concat(bw.escapeHTML(classStr), "\"");
9603
9731
  }
@@ -9633,12 +9761,184 @@
9633
9761
  // Process content recursively
9634
9762
  var contentStr = content != null ? bw.html(content, options) : '';
9635
9763
  // Resolve template bindings in content if state provided
9636
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
9764
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
9637
9765
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
9638
9766
  }
9639
9767
  return "<".concat(tag).concat(attrStr, ">").concat(contentStr, "</").concat(tag, ">");
9640
9768
  };
9641
9769
 
9770
+ /**
9771
+ * Generate a complete, self-contained HTML document from TACO content.
9772
+ *
9773
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
9774
+ * func registry emission (so serialized event handlers work), optional theme,
9775
+ * and extra head elements. Designed for static site generation, offline/airgapped
9776
+ * use, and the "static site that isn't static" workflow.
9777
+ *
9778
+ * @param {Object} [opts={}] - Page options
9779
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
9780
+ * @param {string} [opts.title='bitwrench'] - Page title
9781
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
9782
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
9783
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
9784
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
9785
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
9786
+ * @param {string} [opts.favicon=''] - Favicon URL
9787
+ * @param {string} [opts.lang='en'] - HTML lang attribute
9788
+ * @returns {string} Complete HTML document string
9789
+ * @category DOM Generation
9790
+ * @see bw.html
9791
+ * @example
9792
+ * bw.htmlPage({
9793
+ * title: 'My App',
9794
+ * body: { t: 'h1', c: 'Hello World' },
9795
+ * runtime: 'shim'
9796
+ * })
9797
+ */
9798
+ bw.htmlPage = function (opts) {
9799
+ opts = opts || {};
9800
+ var title = opts.title || 'bitwrench';
9801
+ var body = opts.body || '';
9802
+ var state = opts.state || undefined;
9803
+ var runtime = opts.runtime || 'shim';
9804
+ var css = opts.css || '';
9805
+ var theme = opts.theme || null;
9806
+ var headExtra = opts.head || [];
9807
+ var favicon = opts.favicon || '';
9808
+ var lang = opts.lang || 'en';
9809
+
9810
+ // Snapshot funcRegistry counter before rendering
9811
+ var fnCounterBefore = bw._fnIDCounter;
9812
+
9813
+ // Render body content
9814
+ var bodyHTML = '';
9815
+ if (_is(body, 'string')) {
9816
+ bodyHTML = body;
9817
+ } else {
9818
+ var htmlOpts = {};
9819
+ if (state) htmlOpts.state = state;
9820
+ bodyHTML = bw.html(body, htmlOpts);
9821
+ }
9822
+
9823
+ // Collect functions registered during this render
9824
+ var fnCounterAfter = bw._fnIDCounter;
9825
+ var registryEntries = '';
9826
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
9827
+ var fnKey = 'bw_fn_' + i;
9828
+ if (bw._fnRegistry[fnKey]) {
9829
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' + bw._fnRegistry[fnKey].toString() + ';\n';
9830
+ }
9831
+ }
9832
+
9833
+ // Build runtime script for <head>
9834
+ var runtimeHead = '';
9835
+ if (runtime === 'inline') {
9836
+ // Read UMD bundle synchronously if in Node.js
9837
+ var umdSource = null;
9838
+ if (bw._isNode) {
9839
+ try {
9840
+ var fs = typeof require === 'function' ? require('fs') : null;
9841
+ var pathMod = typeof require === 'function' ? require('path') : null;
9842
+ if (fs && pathMod) {
9843
+ // Resolve dist/ relative to this source file
9844
+ var srcDir = '';
9845
+ try {
9846
+ srcDir = pathMod.dirname(typeof __filename !== 'undefined' ? __filename : '');
9847
+ } catch (e2) {/* ESM: __filename not available */}
9848
+ 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.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.es5.js', document.baseURI).href))) {
9849
+ var url = typeof require === 'function' ? require('url') : null;
9850
+ 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.es5.js', document.baseURI).href))));
9851
+ }
9852
+ if (srcDir) {
9853
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
9854
+ umdSource = fs.readFileSync(distPath, 'utf8');
9855
+ }
9856
+ }
9857
+ } catch (e) {/* fall through */}
9858
+ }
9859
+ if (umdSource) {
9860
+ runtimeHead = '<script>' + umdSource + '</script>';
9861
+ } else {
9862
+ // Fallback to shim in browser or if dist not available
9863
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
9864
+ }
9865
+ } else if (runtime === 'cdn') {
9866
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
9867
+ } else if (runtime === 'shim') {
9868
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
9869
+ }
9870
+ // runtime === 'none' → empty
9871
+
9872
+ // Theme CSS
9873
+ var themeCSS = '';
9874
+ if (theme) {
9875
+ var themeConfig = _is(theme, 'string') ? THEME_PRESETS[theme.toLowerCase()] || null : theme;
9876
+ if (themeConfig) {
9877
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, {
9878
+ inject: false
9879
+ }));
9880
+ themeCSS = themeResult.css;
9881
+ }
9882
+ }
9883
+
9884
+ // Extra <head> elements
9885
+ var headHTML = '';
9886
+ if (_isA(headExtra) && headExtra.length > 0) {
9887
+ headHTML = headExtra.map(function (el) {
9888
+ return bw.html(el);
9889
+ }).join('\n');
9890
+ }
9891
+
9892
+ // Favicon
9893
+ var faviconTag = '';
9894
+ if (favicon) {
9895
+ var safeFavicon = favicon.replace(/[&<>"']/g, function (c) {
9896
+ return {
9897
+ '&': '&amp;',
9898
+ '<': '&lt;',
9899
+ '>': '&gt;',
9900
+ '"': '&quot;',
9901
+ "'": '&#39;'
9902
+ }[c];
9903
+ });
9904
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
9905
+ }
9906
+
9907
+ // Escaped title
9908
+ var safeTitle = bw.escapeHTML(title);
9909
+
9910
+ // Combine all CSS
9911
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
9912
+
9913
+ // Body-end script: registry entries + optional loadDefaultStyles
9914
+ var bodyEndScript = '';
9915
+ var bodyEndParts = [];
9916
+ if (registryEntries) {
9917
+ bodyEndParts.push(registryEntries);
9918
+ }
9919
+ if (runtime === 'inline' || runtime === 'cdn') {
9920
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
9921
+ }
9922
+ if (bodyEndParts.length > 0) {
9923
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
9924
+ }
9925
+
9926
+ // Assemble document
9927
+ var parts = ['<!DOCTYPE html>', '<html lang="' + lang + '">', '<head>', '<meta charset="UTF-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">'];
9928
+ parts.push('<title>' + safeTitle + '</title>');
9929
+ if (faviconTag) parts.push(faviconTag);
9930
+ if (runtimeHead) parts.push(runtimeHead);
9931
+ if (headHTML) parts.push(headHTML);
9932
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
9933
+ parts.push('</head>');
9934
+ parts.push('<body>');
9935
+ parts.push(bodyHTML);
9936
+ if (bodyEndScript) parts.push(bodyEndScript);
9937
+ parts.push('</body>');
9938
+ parts.push('</html>');
9939
+ return parts.join('\n');
9940
+ };
9941
+
9642
9942
  /**
9643
9943
  * Create a live DOM element from a TACO object (browser only).
9644
9944
  *
@@ -9684,7 +9984,7 @@
9684
9984
  }
9685
9985
 
9686
9986
  // Handle text nodes
9687
- if (_typeof(taco) !== 'object' || !taco.t) {
9987
+ if (!_is(taco, 'object') || !taco.t) {
9688
9988
  return document.createTextNode(String(taco));
9689
9989
  }
9690
9990
  var tag = taco.t,
@@ -9703,16 +10003,16 @@
9703
10003
  key = _Object$entries2$_i[0],
9704
10004
  value = _Object$entries2$_i[1];
9705
10005
  if (value == null || value === false) continue;
9706
- if (key === 'style' && _typeof(value) === 'object') {
10006
+ if (key === 'style' && _is(value, 'object')) {
9707
10007
  // Apply styles directly
9708
10008
  Object.assign(el.style, value);
9709
10009
  } else if (key === 'class') {
9710
10010
  // Handle class as array or string
9711
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
10011
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
9712
10012
  if (classStr) {
9713
10013
  el.className = classStr;
9714
10014
  }
9715
- } else if (key.startsWith('on') && typeof value === 'function') {
10015
+ } else if (key.startsWith('on') && _is(value, 'function')) {
9716
10016
  // Event handlers
9717
10017
  var eventName = key.slice(2).toLowerCase();
9718
10018
  el.addEventListener(eventName, value);
@@ -9732,7 +10032,7 @@
9732
10032
  // Children with data-bw_id or id attributes get local refs on the parent,
9733
10033
  // so o.render functions can access them without any DOM lookup.
9734
10034
  if (content != null) {
9735
- if (Array.isArray(content)) {
10035
+ if (_isA(content)) {
9736
10036
  content.forEach(function (child) {
9737
10037
  if (child != null) {
9738
10038
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -9752,20 +10052,20 @@
9752
10052
  if (childEl._bw_refs) {
9753
10053
  if (!el._bw_refs) el._bw_refs = {};
9754
10054
  for (var rk in childEl._bw_refs) {
9755
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
10055
+ if (_hop.call(childEl._bw_refs, rk)) {
9756
10056
  el._bw_refs[rk] = childEl._bw_refs[rk];
9757
10057
  }
9758
10058
  }
9759
10059
  }
9760
10060
  }
9761
10061
  });
9762
- } else if (_typeof(content) === 'object' && content.__bw_raw) {
10062
+ } else if (_is(content, 'object') && content.__bw_raw) {
9763
10063
  // Raw HTML content — inject via innerHTML
9764
10064
  el.innerHTML = content.v;
9765
10065
  } else if (content._bwComponent === true) {
9766
10066
  // Single ComponentHandle as content
9767
10067
  content.mount(el);
9768
- } else if (_typeof(content) === 'object' && content.t) {
10068
+ } else if (_is(content, 'object') && content.t) {
9769
10069
  var childEl = bw.createDOM(content, options);
9770
10070
  el.appendChild(childEl);
9771
10071
  var childBwId = content.a ? content.a['data-bw_id'] || content.a.id : null;
@@ -9776,7 +10076,7 @@
9776
10076
  if (childEl._bw_refs) {
9777
10077
  if (!el._bw_refs) el._bw_refs = {};
9778
10078
  for (var rk in childEl._bw_refs) {
9779
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
10079
+ if (_hop.call(childEl._bw_refs, rk)) {
9780
10080
  el._bw_refs[rk] = childEl._bw_refs[rk];
9781
10081
  }
9782
10082
  }
@@ -9808,7 +10108,7 @@
9808
10108
  if (opts.render) {
9809
10109
  el._bw_render = opts.render;
9810
10110
  if (opts.mounted) {
9811
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
10111
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
9812
10112
  }
9813
10113
 
9814
10114
  // Queue initial render (same timing as mounted)
@@ -9880,7 +10180,7 @@
9880
10180
  // Get target element (use cache-backed lookup)
9881
10181
  var targetEl = bw._el(target);
9882
10182
  if (!targetEl) {
9883
- console.error('bw.DOM: Target element not found:', target);
10183
+ _ce('bw.DOM: Target element not found:', target);
9884
10184
  return null;
9885
10185
  }
9886
10186
 
@@ -9918,7 +10218,7 @@
9918
10218
  targetEl.appendChild(taco.element);
9919
10219
  }
9920
10220
  // Handle arrays
9921
- else if (Array.isArray(taco)) {
10221
+ else if (_isA(taco)) {
9922
10222
  taco.forEach(function (t) {
9923
10223
  if (t != null) {
9924
10224
  if (t._bwComponent === true) {
@@ -9953,7 +10253,7 @@
9953
10253
  bw.compileProps = function (handle) {
9954
10254
  var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9955
10255
  var compiledProps = {};
9956
- Object.keys(props).forEach(function (key) {
10256
+ _keys(props).forEach(function (key) {
9957
10257
  // Create getter/setter for each prop
9958
10258
  Object.defineProperty(compiledProps, key, {
9959
10259
  get: function get() {
@@ -10264,17 +10564,17 @@
10264
10564
  if (attr) {
10265
10565
  // Patch an attribute
10266
10566
  el.setAttribute(attr, String(content));
10267
- } else if (Array.isArray(content)) {
10567
+ } else if (_isA(content)) {
10268
10568
  // Patch with array of children (strings and/or TACOs)
10269
10569
  el.innerHTML = '';
10270
10570
  content.forEach(function (item) {
10271
- if (typeof item === 'string' || typeof item === 'number') {
10571
+ if (_is(item, 'string') || _is(item, 'number')) {
10272
10572
  el.appendChild(document.createTextNode(String(item)));
10273
10573
  } else if (item && item.t) {
10274
10574
  el.appendChild(bw.createDOM(item));
10275
10575
  }
10276
10576
  });
10277
- } else if (_typeof(content) === 'object' && content !== null && content.t) {
10577
+ } else if (_is(content, 'object') && content.t) {
10278
10578
  // Patch with a TACO — replace children
10279
10579
  el.innerHTML = '';
10280
10580
  el.appendChild(bw.createDOM(content));
@@ -10305,7 +10605,7 @@
10305
10605
  bw.patchAll = function (patches) {
10306
10606
  var results = {};
10307
10607
  for (var id in patches) {
10308
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
10608
+ if (_hop.call(patches, id)) {
10309
10609
  results[id] = bw.patch(id, patches[id]);
10310
10610
  }
10311
10611
  }
@@ -10402,7 +10702,7 @@
10402
10702
  snapshot[i].handler(detail);
10403
10703
  called++;
10404
10704
  } catch (err) {
10405
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
10705
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
10406
10706
  }
10407
10707
  }
10408
10708
  return called;
@@ -10503,8 +10803,8 @@
10503
10803
  * @see bw.funcGetDispatchStr
10504
10804
  */
10505
10805
  bw.funcRegister = function (fn, name) {
10506
- if (typeof fn !== 'function') return '';
10507
- var fnID = typeof name === 'string' && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
10806
+ if (!_is(fn, 'function')) return '';
10807
+ var fnID = _is(name, 'string') && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
10508
10808
  bw._fnRegistry[fnID] = fn;
10509
10809
  return fnID;
10510
10810
  };
@@ -10523,8 +10823,8 @@
10523
10823
  bw.funcGetById = function (name, errFn) {
10524
10824
  name = String(name);
10525
10825
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
10526
- return typeof errFn === 'function' ? errFn : function () {
10527
- console.warn('bw.funcGetById: unregistered fn "' + name + '"');
10826
+ return _is(errFn, 'function') ? errFn : function () {
10827
+ _cw('bw.funcGetById: unregistered fn "' + name + '"');
10528
10828
  };
10529
10829
  };
10530
10830
 
@@ -10566,13 +10866,23 @@
10566
10866
  bw.funcGetRegistry = function () {
10567
10867
  var copy = {};
10568
10868
  for (var k in bw._fnRegistry) {
10569
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
10869
+ if (_hop.call(bw._fnRegistry, k)) {
10570
10870
  copy[k] = bw._fnRegistry[k];
10571
10871
  }
10572
10872
  }
10573
10873
  return copy;
10574
10874
  };
10575
10875
 
10876
+ /**
10877
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
10878
+ * When embedded in a `<script>` tag, provides just enough infrastructure
10879
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
10880
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
10881
+ * @type {string}
10882
+ * @category Function Registry
10883
+ */
10884
+ 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;})();';
10885
+
10576
10886
  // ===================================================================================
10577
10887
  // Template Binding Utilities
10578
10888
  // ===================================================================================
@@ -10604,7 +10914,10 @@
10604
10914
  var parts = path.split('.');
10605
10915
  var val = state;
10606
10916
  for (var i = 0; i < parts.length; i++) {
10607
- if (val == null) return '';
10917
+ if (val == null) {
10918
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
10919
+ return '';
10920
+ }
10608
10921
  val = val[parts[i]];
10609
10922
  }
10610
10923
  return val == null ? '' : val;
@@ -10624,7 +10937,7 @@
10624
10937
  */
10625
10938
  bw._compiledExprs = {};
10626
10939
  bw._resolveTemplate = function (str, state, compile) {
10627
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
10940
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
10628
10941
  var bindings = bw._parseBindings(str);
10629
10942
  if (bindings.length === 0) return str;
10630
10943
  var result = '';
@@ -10647,6 +10960,7 @@
10647
10960
  try {
10648
10961
  val = bw._compiledExprs[b.expr](state);
10649
10962
  } catch (e) {
10963
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
10650
10964
  val = '';
10651
10965
  }
10652
10966
  } else {
@@ -10754,7 +11068,7 @@
10754
11068
  this._state = {};
10755
11069
  if (o.state) {
10756
11070
  for (var k in o.state) {
10757
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
11071
+ if (_hop.call(o.state, k)) {
10758
11072
  this._state[k] = o.state[k];
10759
11073
  }
10760
11074
  }
@@ -10763,7 +11077,7 @@
10763
11077
  this._actions = {};
10764
11078
  if (o.actions) {
10765
11079
  for (var k2 in o.actions) {
10766
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
11080
+ if (_hop.call(o.actions, k2)) {
10767
11081
  this._actions[k2] = o.actions[k2];
10768
11082
  }
10769
11083
  }
@@ -10773,7 +11087,7 @@
10773
11087
  if (o.methods) {
10774
11088
  var self = this;
10775
11089
  for (var k3 in o.methods) {
10776
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
11090
+ if (_hop.call(o.methods, k3)) {
10777
11091
  this._methods[k3] = o.methods[k3];
10778
11092
  (function (methodName, methodFn) {
10779
11093
  self[methodName] = function () {
@@ -10806,14 +11120,23 @@
10806
11120
  this._compile = !!o.compile;
10807
11121
  this._bw_refs = {};
10808
11122
  this._refCounter = 0;
11123
+ // Child component ownership (Bug #5)
11124
+ this._children = [];
11125
+ this._parent = null;
11126
+ // Factory metadata for BCCL rebuild (Bug #6)
11127
+ this._factory = taco._bwFactory || null;
10809
11128
  }
10810
11129
 
11130
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
11131
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
11132
+ var _chp = ComponentHandle.prototype;
11133
+
10811
11134
  // ── State Methods ──
10812
11135
 
10813
11136
  /**
10814
11137
  * Get a state value. Dot-path supported: `get('user.name')`
10815
11138
  */
10816
- ComponentHandle.prototype.get = function (key) {
11139
+ _chp.get = function (key) {
10817
11140
  return bw._evaluatePath(this._state, key);
10818
11141
  };
10819
11142
 
@@ -10823,12 +11146,13 @@
10823
11146
  * @param {*} value - New value
10824
11147
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
10825
11148
  */
10826
- ComponentHandle.prototype.set = function (key, value, opts) {
11149
+ _chp.set = function (key, value, opts) {
10827
11150
  // Dot-path set
10828
11151
  var parts = key.split('.');
10829
11152
  var obj = this._state;
10830
11153
  for (var i = 0; i < parts.length - 1; i++) {
10831
- if (obj[parts[i]] == null || _typeof(obj[parts[i]]) !== 'object') {
11154
+ if (!_is(obj[parts[i]], 'object')) {
11155
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
10832
11156
  obj[parts[i]] = {};
10833
11157
  }
10834
11158
  obj = obj[parts[i]];
@@ -10848,10 +11172,10 @@
10848
11172
  /**
10849
11173
  * Get a shallow clone of the full state.
10850
11174
  */
10851
- ComponentHandle.prototype.getState = function () {
11175
+ _chp.getState = function () {
10852
11176
  var clone = {};
10853
11177
  for (var k in this._state) {
10854
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
11178
+ if (_hop.call(this._state, k)) {
10855
11179
  clone[k] = this._state[k];
10856
11180
  }
10857
11181
  }
@@ -10863,9 +11187,9 @@
10863
11187
  * @param {Object} updates - Key-value pairs to merge
10864
11188
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
10865
11189
  */
10866
- ComponentHandle.prototype.setState = function (updates, opts) {
11190
+ _chp.setState = function (updates, opts) {
10867
11191
  for (var k in updates) {
10868
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
11192
+ if (_hop.call(updates, k)) {
10869
11193
  this._state[k] = updates[k];
10870
11194
  this._dirtyKeys[k] = true;
10871
11195
  }
@@ -10882,9 +11206,9 @@
10882
11206
  /**
10883
11207
  * Push a value onto an array in state. Clones the array.
10884
11208
  */
10885
- ComponentHandle.prototype.push = function (key, val) {
11209
+ _chp.push = function (key, val) {
10886
11210
  var arr = this.get(key);
10887
- var newArr = Array.isArray(arr) ? arr.slice() : [];
11211
+ var newArr = _isA(arr) ? arr.slice() : [];
10888
11212
  newArr.push(val);
10889
11213
  this.set(key, newArr);
10890
11214
  };
@@ -10892,9 +11216,9 @@
10892
11216
  /**
10893
11217
  * Splice an array in state. Clones the array.
10894
11218
  */
10895
- ComponentHandle.prototype.splice = function (key, start, deleteCount) {
11219
+ _chp.splice = function (key, start, deleteCount) {
10896
11220
  var arr = this.get(key);
10897
- var newArr = Array.isArray(arr) ? arr.slice() : [];
11221
+ var newArr = _isA(arr) ? arr.slice() : [];
10898
11222
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
10899
11223
  Array.prototype.splice.apply(newArr, args);
10900
11224
  this.set(key, newArr);
@@ -10902,7 +11226,7 @@
10902
11226
 
10903
11227
  // ── Scheduling ──
10904
11228
 
10905
- ComponentHandle.prototype._scheduleDirty = function () {
11229
+ _chp._scheduleDirty = function () {
10906
11230
  if (!this._scheduled) {
10907
11231
  this._scheduled = true;
10908
11232
  bw._dirtyComponents.push(this);
@@ -10917,16 +11241,16 @@
10917
11241
  * Creates binding descriptors with refIds for targeted DOM updates.
10918
11242
  * @private
10919
11243
  */
10920
- ComponentHandle.prototype._compileBindings = function () {
11244
+ _chp._compileBindings = function () {
10921
11245
  this._bindings = [];
10922
11246
  this._refCounter = 0;
10923
- var stateKeys = Object.keys(this._state);
11247
+ var stateKeys = _keys(this._state);
10924
11248
  var self = this;
10925
11249
  function walkTaco(taco, path) {
10926
- if (taco == null || _typeof(taco) !== 'object' || !taco.t) return taco;
11250
+ if (!_is(taco, 'object') || !taco.t) return taco;
10927
11251
 
10928
11252
  // Check content for bindings
10929
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
11253
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
10930
11254
  var refId = 'bw_ref_' + self._refCounter++;
10931
11255
  var parsed = bw._parseBindings(taco.c);
10932
11256
  var deps = [];
@@ -10948,10 +11272,10 @@
10948
11272
  // Check attributes for bindings
10949
11273
  if (taco.a) {
10950
11274
  for (var attrName in taco.a) {
10951
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
11275
+ if (!_hop.call(taco.a, attrName)) continue;
10952
11276
  if (attrName === 'data-bw_ref') continue;
10953
11277
  var attrVal = taco.a[attrName];
10954
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
11278
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
10955
11279
  var refId2 = 'bw_ref_' + self._refCounter++;
10956
11280
  var parsed2 = bw._parseBindings(attrVal);
10957
11281
  var deps2 = [];
@@ -10977,9 +11301,34 @@
10977
11301
  }
10978
11302
 
10979
11303
  // Recurse into children
10980
- if (Array.isArray(taco.c)) {
11304
+ if (_isA(taco.c)) {
10981
11305
  for (var i = 0; i < taco.c.length; i++) {
10982
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
11306
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
11307
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
11308
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
11309
+ var mixedParsed = bw._parseBindings(taco.c[i]);
11310
+ var mixedDeps = [];
11311
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
11312
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
11313
+ }
11314
+ self._bindings.push({
11315
+ expr: taco.c[i],
11316
+ type: 'content',
11317
+ refId: mixedRefId,
11318
+ deps: mixedDeps,
11319
+ template: taco.c[i]
11320
+ });
11321
+ // Replace string with a span wrapper so textContent targets the span only
11322
+ taco.c[i] = {
11323
+ t: 'span',
11324
+ a: {
11325
+ 'data-bw_ref': mixedRefId,
11326
+ style: 'display:contents'
11327
+ },
11328
+ c: taco.c[i]
11329
+ };
11330
+ }
11331
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
10983
11332
  walkTaco(taco.c[i], path.concat(i));
10984
11333
  }
10985
11334
  // Handle bw.when/bw.each markers
@@ -11014,7 +11363,7 @@
11014
11363
  taco.c[i]._refId = eachRefId;
11015
11364
  }
11016
11365
  }
11017
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11366
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11018
11367
  walkTaco(taco.c, path.concat(0));
11019
11368
  }
11020
11369
  return taco;
@@ -11028,7 +11377,7 @@
11028
11377
  * Build ref map from the live DOM after createDOM.
11029
11378
  * @private
11030
11379
  */
11031
- ComponentHandle.prototype._collectRefs = function () {
11380
+ _chp._collectRefs = function () {
11032
11381
  this._bw_refs = {};
11033
11382
  if (!this.element) return;
11034
11383
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -11049,7 +11398,7 @@
11049
11398
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
11050
11399
  * @param {Element} parentEl - DOM element to mount into
11051
11400
  */
11052
- ComponentHandle.prototype.mount = function (parentEl) {
11401
+ _chp.mount = function (parentEl) {
11053
11402
  // willMount hook
11054
11403
  if (this._hooks.willMount) this._hooks.willMount(this);
11055
11404
 
@@ -11071,7 +11420,7 @@
11071
11420
  // Register named actions in function registry
11072
11421
  var self = this;
11073
11422
  for (var actionName in this._actions) {
11074
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
11423
+ if (_hop.call(this._actions, actionName)) {
11075
11424
  var registeredName = this._bwId + '_' + actionName;
11076
11425
  (function (aName) {
11077
11426
  bw.funcRegister(function (evt) {
@@ -11090,6 +11439,11 @@
11090
11439
  this.element = bw.createDOM(tacoForDOM);
11091
11440
  this.element._bwComponentHandle = this;
11092
11441
  this.element.setAttribute('data-bw_comp_id', this._bwId);
11442
+
11443
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
11444
+ if (this.taco.o && this.taco.o.render) {
11445
+ this.element._bw_render = this.taco.o.render;
11446
+ }
11093
11447
  if (this._userTag) {
11094
11448
  this.element.classList.add(this._userTag);
11095
11449
  }
@@ -11104,6 +11458,16 @@
11104
11458
  this._resolveAndApplyAll();
11105
11459
  this.mounted = true;
11106
11460
 
11461
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
11462
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
11463
+ for (var ci = 0; ci < childEls.length; ci++) {
11464
+ var ch = childEls[ci]._bwComponentHandle;
11465
+ if (ch && ch !== this && !ch._parent) {
11466
+ ch._parent = this;
11467
+ this._children.push(ch);
11468
+ }
11469
+ }
11470
+
11107
11471
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
11108
11472
  if (this._hooks.mounted) {
11109
11473
  if (this._hooks.mounted.length === 2) {
@@ -11112,15 +11476,20 @@
11112
11476
  this._hooks.mounted(this);
11113
11477
  }
11114
11478
  }
11479
+
11480
+ // Invoke o.render on initial mount (if present)
11481
+ if (this.element._bw_render) {
11482
+ this.element._bw_render(this.element, this._state);
11483
+ }
11115
11484
  };
11116
11485
 
11117
11486
  /**
11118
11487
  * Prepare TACO for initial render: resolve when/each markers.
11119
11488
  * @private
11120
11489
  */
11121
- ComponentHandle.prototype._prepareTaco = function (taco) {
11122
- if (!taco || _typeof(taco) !== 'object') return;
11123
- if (Array.isArray(taco.c)) {
11490
+ _chp._prepareTaco = function (taco) {
11491
+ if (!_is(taco, 'object')) return;
11492
+ if (_isA(taco.c)) {
11124
11493
  for (var i = taco.c.length - 1; i >= 0; i--) {
11125
11494
  var child = taco.c[i];
11126
11495
  if (child && child._bwWhen) {
@@ -11161,7 +11530,7 @@
11161
11530
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
11162
11531
  var arr = bw._evaluatePath(this._state, eachExprStr);
11163
11532
  var items = [];
11164
- if (Array.isArray(arr)) {
11533
+ if (_isA(arr)) {
11165
11534
  for (var j = 0; j < arr.length; j++) {
11166
11535
  items.push(child.factory(arr[j], j));
11167
11536
  }
@@ -11175,11 +11544,11 @@
11175
11544
  c: items
11176
11545
  };
11177
11546
  }
11178
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
11547
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
11179
11548
  this._prepareTaco(taco.c[i]);
11180
11549
  }
11181
11550
  }
11182
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11551
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11183
11552
  this._prepareTaco(taco.c);
11184
11553
  }
11185
11554
  };
@@ -11188,12 +11557,12 @@
11188
11557
  * Wire action name strings (in onclick etc.) to dispatch function calls.
11189
11558
  * @private
11190
11559
  */
11191
- ComponentHandle.prototype._wireActions = function (taco) {
11192
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return;
11560
+ _chp._wireActions = function (taco) {
11561
+ if (!_is(taco, 'object') || !taco.t) return;
11193
11562
  if (taco.a) {
11194
11563
  for (var key in taco.a) {
11195
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
11196
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
11564
+ if (!_hop.call(taco.a, key)) continue;
11565
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
11197
11566
  var actionName = taco.a[key];
11198
11567
  if (actionName in this._actions) {
11199
11568
  var registeredName = this._bwId + '_' + actionName;
@@ -11207,11 +11576,11 @@
11207
11576
  }
11208
11577
  }
11209
11578
  }
11210
- if (Array.isArray(taco.c)) {
11579
+ if (_isA(taco.c)) {
11211
11580
  for (var i = 0; i < taco.c.length; i++) {
11212
11581
  this._wireActions(taco.c[i]);
11213
11582
  }
11214
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11583
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11215
11584
  this._wireActions(taco.c);
11216
11585
  }
11217
11586
  };
@@ -11220,7 +11589,7 @@
11220
11589
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
11221
11590
  * @private
11222
11591
  */
11223
- ComponentHandle.prototype._deepCloneTaco = function (taco) {
11592
+ _chp._deepCloneTaco = function (taco) {
11224
11593
  if (taco == null) return taco;
11225
11594
  // Preserve _bwWhen / _bwEach markers (contain functions)
11226
11595
  if (taco._bwWhen) {
@@ -11239,22 +11608,22 @@
11239
11608
  _refId: taco._refId
11240
11609
  };
11241
11610
  }
11242
- if (_typeof(taco) !== 'object' || !taco.t) return taco;
11611
+ if (!_is(taco, 'object') || !taco.t) return taco;
11243
11612
  var result = {
11244
11613
  t: taco.t
11245
11614
  };
11246
11615
  if (taco.a) {
11247
11616
  result.a = {};
11248
11617
  for (var k in taco.a) {
11249
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
11618
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
11250
11619
  }
11251
11620
  }
11252
11621
  if (taco.c != null) {
11253
- if (Array.isArray(taco.c)) {
11622
+ if (_isA(taco.c)) {
11254
11623
  result.c = taco.c.map(function (child) {
11255
11624
  return this._deepCloneTaco(child);
11256
11625
  }.bind(this));
11257
- } else if (_typeof(taco.c) === 'object') {
11626
+ } else if (_is(taco.c, 'object')) {
11258
11627
  result.c = this._deepCloneTaco(taco.c);
11259
11628
  } else {
11260
11629
  result.c = taco.c;
@@ -11268,31 +11637,34 @@
11268
11637
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
11269
11638
  * @private
11270
11639
  */
11271
- ComponentHandle.prototype._tacoForDOM = function (taco) {
11272
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return taco;
11640
+ _chp._tacoForDOM = function (taco) {
11641
+ if (!_is(taco, 'object') || !taco.t) return taco;
11273
11642
  var result = {
11274
11643
  t: taco.t
11275
11644
  };
11276
11645
  if (taco.a) result.a = taco.a;
11277
11646
  if (taco.c != null) {
11278
- if (Array.isArray(taco.c)) {
11647
+ if (_isA(taco.c)) {
11279
11648
  result.c = taco.c.map(function (child) {
11280
11649
  return this._tacoForDOM(child);
11281
11650
  }.bind(this));
11282
- } else if (_typeof(taco.c) === 'object' && taco.c.t) {
11651
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11283
11652
  result.c = this._tacoForDOM(taco.c);
11284
11653
  } else {
11285
11654
  result.c = taco.c;
11286
11655
  }
11287
11656
  }
11288
11657
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
11658
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
11659
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t + '>. Use onclick attribute or bw.component() for child interactivity.');
11660
+ }
11289
11661
  return result;
11290
11662
  };
11291
11663
 
11292
11664
  /**
11293
11665
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
11294
11666
  */
11295
- ComponentHandle.prototype.unmount = function () {
11667
+ _chp.unmount = function () {
11296
11668
  if (!this.mounted) return;
11297
11669
 
11298
11670
  // unmount hook
@@ -11326,11 +11698,22 @@
11326
11698
  /**
11327
11699
  * Destroy: unmount + clear state + unregister actions.
11328
11700
  */
11329
- ComponentHandle.prototype.destroy = function () {
11701
+ _chp.destroy = function () {
11330
11702
  // willDestroy hook
11331
11703
  if (this._hooks.willDestroy) {
11332
11704
  this._hooks.willDestroy(this);
11333
11705
  }
11706
+
11707
+ // Cascade destroy to children depth-first (Bug #5)
11708
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
11709
+ this._children[ci].destroy();
11710
+ }
11711
+ this._children = [];
11712
+ if (this._parent) {
11713
+ var idx = this._parent._children.indexOf(this);
11714
+ if (idx >= 0) this._parent._children.splice(idx, 1);
11715
+ this._parent = null;
11716
+ }
11334
11717
  this.unmount();
11335
11718
 
11336
11719
  // Unregister actions from function registry
@@ -11357,12 +11740,37 @@
11357
11740
  * Flush dirty state: resolve changed bindings and apply to DOM.
11358
11741
  * @private
11359
11742
  */
11360
- ComponentHandle.prototype._flush = function () {
11743
+ _chp._flush = function () {
11361
11744
  this._scheduled = false;
11362
- var changedKeys = Object.keys(this._dirtyKeys);
11745
+ var changedKeys = _keys(this._dirtyKeys);
11363
11746
  this._dirtyKeys = {};
11364
11747
  if (changedKeys.length === 0 || !this.mounted) return;
11365
11748
 
11749
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
11750
+ // rebuild the TACO from the factory with merged state (Bug #6)
11751
+ if (this._factory) {
11752
+ var rebuildNeeded = false;
11753
+ for (var fi = 0; fi < changedKeys.length; fi++) {
11754
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
11755
+ rebuildNeeded = true;
11756
+ break;
11757
+ }
11758
+ }
11759
+ if (rebuildNeeded) {
11760
+ var merged = {};
11761
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
11762
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
11763
+ this._factory.props = merged;
11764
+ var newTaco = bw.make(this._factory.type, merged);
11765
+ newTaco._bwFactory = this._factory;
11766
+ this.taco = newTaco;
11767
+ this._originalTaco = this._deepCloneTaco(newTaco);
11768
+ this._render();
11769
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
11770
+ return;
11771
+ }
11772
+ }
11773
+
11366
11774
  // willUpdate hook
11367
11775
  if (this._hooks.willUpdate) {
11368
11776
  this._hooks.willUpdate(this, changedKeys);
@@ -11400,7 +11808,7 @@
11400
11808
  * Returns list of patches to apply.
11401
11809
  * @private
11402
11810
  */
11403
- ComponentHandle.prototype._resolveBindings = function (changedKeys) {
11811
+ _chp._resolveBindings = function (changedKeys) {
11404
11812
  var patches = [];
11405
11813
  for (var i = 0; i < this._bindings.length; i++) {
11406
11814
  var b = this._bindings[i];
@@ -11436,11 +11844,14 @@
11436
11844
  * Apply patches to DOM.
11437
11845
  * @private
11438
11846
  */
11439
- ComponentHandle.prototype._applyPatches = function (patches) {
11847
+ _chp._applyPatches = function (patches) {
11440
11848
  for (var i = 0; i < patches.length; i++) {
11441
11849
  var p = patches[i];
11442
11850
  var el = this._bw_refs[p.refId];
11443
- if (!el) continue;
11851
+ if (!el) {
11852
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
11853
+ continue;
11854
+ }
11444
11855
  if (p.type === 'content') {
11445
11856
  el.textContent = p.value;
11446
11857
  } else if (p.type === 'attribute') {
@@ -11457,7 +11868,7 @@
11457
11868
  * Resolve all bindings and apply (used for initial render).
11458
11869
  * @private
11459
11870
  */
11460
- ComponentHandle.prototype._resolveAndApplyAll = function () {
11871
+ _chp._resolveAndApplyAll = function () {
11461
11872
  var patches = [];
11462
11873
  for (var i = 0; i < this._bindings.length; i++) {
11463
11874
  var b = this._bindings[i];
@@ -11479,7 +11890,7 @@
11479
11890
  * Full re-render for structural changes (when/each branch switches).
11480
11891
  * @private
11481
11892
  */
11482
- ComponentHandle.prototype._render = function () {
11893
+ _chp._render = function () {
11483
11894
  if (!this.element || !this.element.parentNode) return;
11484
11895
  var parent = this.element.parentNode;
11485
11896
  var nextSibling = this.element.nextSibling;
@@ -11518,7 +11929,7 @@
11518
11929
  * @param {string} event - Event name (e.g., 'click')
11519
11930
  * @param {Function} handler - Event handler
11520
11931
  */
11521
- ComponentHandle.prototype.on = function (event, handler) {
11932
+ _chp.on = function (event, handler) {
11522
11933
  if (this.element) {
11523
11934
  this.element.addEventListener(event, handler);
11524
11935
  }
@@ -11533,7 +11944,7 @@
11533
11944
  * @param {string} event - Event name
11534
11945
  * @param {Function} handler - Handler to remove
11535
11946
  */
11536
- ComponentHandle.prototype.off = function (event, handler) {
11947
+ _chp.off = function (event, handler) {
11537
11948
  if (this.element) {
11538
11949
  this.element.removeEventListener(event, handler);
11539
11950
  }
@@ -11548,7 +11959,7 @@
11548
11959
  * @param {Function} handler - Handler function
11549
11960
  * @returns {Function} Unsubscribe function
11550
11961
  */
11551
- ComponentHandle.prototype.sub = function (topic, handler) {
11962
+ _chp.sub = function (topic, handler) {
11552
11963
  var unsub = bw.sub(topic, handler);
11553
11964
  this._subs.push(unsub);
11554
11965
  return unsub;
@@ -11559,10 +11970,10 @@
11559
11970
  * @param {string} name - Action name
11560
11971
  * @param {...*} args - Arguments passed after comp
11561
11972
  */
11562
- ComponentHandle.prototype.action = function (name) {
11973
+ _chp.action = function (name) {
11563
11974
  var fn = this._actions[name];
11564
11975
  if (!fn) {
11565
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
11976
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
11566
11977
  return;
11567
11978
  }
11568
11979
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -11574,7 +11985,7 @@
11574
11985
  * @param {string} sel - CSS selector
11575
11986
  * @returns {Element|null}
11576
11987
  */
11577
- ComponentHandle.prototype.select = function (sel) {
11988
+ _chp.select = function (sel) {
11578
11989
  return this.element ? this.element.querySelector(sel) : null;
11579
11990
  };
11580
11991
 
@@ -11583,7 +11994,7 @@
11583
11994
  * @param {string} sel - CSS selector
11584
11995
  * @returns {Element[]}
11585
11996
  */
11586
- ComponentHandle.prototype.selectAll = function (sel) {
11997
+ _chp.selectAll = function (sel) {
11587
11998
  if (!this.element) return [];
11588
11999
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
11589
12000
  };
@@ -11594,7 +12005,7 @@
11594
12005
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
11595
12006
  * @returns {ComponentHandle} this (for chaining)
11596
12007
  */
11597
- ComponentHandle.prototype.userTag = function (tag) {
12008
+ _chp.userTag = function (tag) {
11598
12009
  this._userTag = tag;
11599
12010
  if (this.element) {
11600
12011
  this.element.classList.add(tag);
@@ -11703,14 +12114,384 @@
11703
12114
  }
11704
12115
  if (!el || !el._bwComponentHandle) return false;
11705
12116
  var comp = el._bwComponentHandle;
11706
- if (typeof comp[action] !== 'function') {
11707
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
12117
+ if (!_is(comp[action], 'function')) {
12118
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
11708
12119
  return false;
11709
12120
  }
11710
12121
  comp[action](data);
11711
12122
  return true;
11712
12123
  };
11713
12124
 
12125
+ // ===================================================================================
12126
+ // bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
12127
+ // ===================================================================================
12128
+
12129
+ /**
12130
+ * Registry of named functions sent via register messages.
12131
+ * Populated by clientApply({ type: 'register', name, body }).
12132
+ * Invoked by clientApply({ type: 'call', name, args }).
12133
+ * @private
12134
+ */
12135
+ bw._clientFunctions = {};
12136
+
12137
+ /**
12138
+ * Whether exec messages are allowed. Set by clientConnect opts.allowExec.
12139
+ * Default false — exec messages are rejected unless explicitly opted in.
12140
+ * @private
12141
+ */
12142
+ bw._allowExec = false;
12143
+
12144
+ /**
12145
+ * Built-in client functions available via call() without registration.
12146
+ * @private
12147
+ */
12148
+ bw._builtinClientFunctions = {
12149
+ scrollTo: function scrollTo(selector) {
12150
+ var el = bw._el(selector);
12151
+ if (el) el.scrollTop = el.scrollHeight;
12152
+ },
12153
+ focus: function focus(selector) {
12154
+ var el = bw._el(selector);
12155
+ if (el && _is(el.focus, 'function')) el.focus();
12156
+ },
12157
+ download: function download(filename, content, mimeType) {
12158
+ if (typeof document === 'undefined') return;
12159
+ var blob = new Blob([content], {
12160
+ type: mimeType || 'text/plain'
12161
+ });
12162
+ var a = document.createElement('a');
12163
+ a.href = URL.createObjectURL(blob);
12164
+ a.download = filename;
12165
+ a.click();
12166
+ URL.revokeObjectURL(a.href);
12167
+ },
12168
+ clipboard: function clipboard(text) {
12169
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
12170
+ navigator.clipboard.writeText(text);
12171
+ }
12172
+ },
12173
+ redirect: function redirect(url) {
12174
+ if (typeof window !== 'undefined') window.location.href = url;
12175
+ },
12176
+ log: function log() {
12177
+ console.log.apply(console, arguments);
12178
+ }
12179
+ };
12180
+
12181
+ /**
12182
+ * Parse a bwserve protocol message string, supporting both strict JSON
12183
+ * and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
12184
+ *
12185
+ * The r-prefix format is designed for C/C++ string literals where
12186
+ * double-quote escaping is painful. The parser is a state machine
12187
+ * that walks character by character — not a regex replace.
12188
+ *
12189
+ * Escaping: apostrophes inside single-quoted values must be escaped
12190
+ * with backslash: r{'name':'Barry\'s room'}
12191
+ *
12192
+ * @param {string} str - JSON or r-prefixed relaxed JSON string
12193
+ * @returns {Object} Parsed message object
12194
+ * @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
12195
+ * @category Server
12196
+ */
12197
+ bw.clientParse = function (str) {
12198
+ str = (str || '').trim();
12199
+ if (str.charAt(0) !== 'r') return JSON.parse(str);
12200
+ str = str.slice(1);
12201
+ var out = [];
12202
+ var i = 0;
12203
+ var len = str.length;
12204
+ while (i < len) {
12205
+ var ch = str[i];
12206
+ if (ch === "'") {
12207
+ // Single-quoted string → emit as double-quoted
12208
+ out.push('"');
12209
+ i++;
12210
+ while (i < len) {
12211
+ var c = str[i];
12212
+ if (c === '\\' && i + 1 < len) {
12213
+ var next = str[i + 1];
12214
+ if (next === "'") {
12215
+ out.push("'"); // \' in input → ' in output
12216
+ } else {
12217
+ out.push('\\');
12218
+ out.push(next);
12219
+ }
12220
+ i += 2;
12221
+ } else if (c === '"') {
12222
+ out.push('\\"');
12223
+ i++;
12224
+ } else if (c === "'") {
12225
+ break;
12226
+ } else {
12227
+ out.push(c);
12228
+ i++;
12229
+ }
12230
+ }
12231
+ out.push('"');
12232
+ i++; // skip closing '
12233
+ } else if (ch === '"') {
12234
+ // Double-quoted string — pass through verbatim
12235
+ out.push(ch);
12236
+ i++;
12237
+ while (i < len) {
12238
+ var c2 = str[i];
12239
+ if (c2 === '\\' && i + 1 < len) {
12240
+ out.push(c2);
12241
+ out.push(str[i + 1]);
12242
+ i += 2;
12243
+ } else {
12244
+ out.push(c2);
12245
+ i++;
12246
+ if (c2 === '"') break;
12247
+ }
12248
+ }
12249
+ } else if (ch === ',') {
12250
+ // Trailing comma check: skip comma if next non-whitespace is } or ]
12251
+ var j = i + 1;
12252
+ while (j < len && (str[j] === ' ' || str[j] === '\t' || str[j] === '\n' || str[j] === '\r')) j++;
12253
+ if (j < len && (str[j] === '}' || str[j] === ']')) {
12254
+ i++; // skip trailing comma
12255
+ } else {
12256
+ out.push(ch);
12257
+ i++;
12258
+ }
12259
+ } else {
12260
+ out.push(ch);
12261
+ i++;
12262
+ }
12263
+ }
12264
+ return JSON.parse(out.join(''));
12265
+ };
12266
+
12267
+ /**
12268
+ * Apply a bwserve protocol message to the DOM.
12269
+ *
12270
+ * Dispatches one of 9 message types:
12271
+ * replace — bw.DOM(target, node)
12272
+ * append — target.appendChild(bw.createDOM(node))
12273
+ * remove — bw.cleanup(target); target.remove()
12274
+ * patch — bw.patch(target, content, attr)
12275
+ * batch — iterate ops, call clientApply for each
12276
+ * message — bw.message(target, action, data)
12277
+ * register — store a named function for later call()
12278
+ * call — invoke a registered or built-in function
12279
+ * exec — execute arbitrary JS (requires allowExec)
12280
+ *
12281
+ * Target resolution:
12282
+ * Starts with '#' or '.' → CSS selector (querySelector)
12283
+ * Otherwise → getElementById, then bw._el fallback
12284
+ *
12285
+ * @param {Object} msg - Protocol message
12286
+ * @returns {boolean} true if the message was applied successfully
12287
+ * @category Server
12288
+ */
12289
+ bw.clientApply = function (msg) {
12290
+ if (!msg || !msg.type) return false;
12291
+ var type = msg.type;
12292
+ var target = msg.target;
12293
+ if (type === 'replace') {
12294
+ var el = bw._el(target);
12295
+ if (!el) return false;
12296
+ bw.DOM(el, msg.node);
12297
+ return true;
12298
+ } else if (type === 'patch') {
12299
+ var patched = bw.patch(target, msg.content, msg.attr);
12300
+ return patched !== null;
12301
+ } else if (type === 'append') {
12302
+ var parent = bw._el(target);
12303
+ if (!parent) return false;
12304
+ var child = bw.createDOM(msg.node);
12305
+ parent.appendChild(child);
12306
+ return true;
12307
+ } else if (type === 'remove') {
12308
+ var toRemove = bw._el(target);
12309
+ if (!toRemove) return false;
12310
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
12311
+ toRemove.remove();
12312
+ return true;
12313
+ } else if (type === 'batch') {
12314
+ if (!_isA(msg.ops)) return false;
12315
+ var allOk = true;
12316
+ msg.ops.forEach(function (op) {
12317
+ if (!bw.clientApply(op)) allOk = false;
12318
+ });
12319
+ return allOk;
12320
+ } else if (type === 'message') {
12321
+ return bw.message(msg.target, msg.action, msg.data);
12322
+ } else if (type === 'register') {
12323
+ if (!msg.name || !msg.body) return false;
12324
+ try {
12325
+ bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
12326
+ return true;
12327
+ } catch (e) {
12328
+ _ce('[bw] register error:', msg.name, e);
12329
+ return false;
12330
+ }
12331
+ } else if (type === 'call') {
12332
+ if (!msg.name) return false;
12333
+ var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
12334
+ if (!_is(fn, 'function')) return false;
12335
+ try {
12336
+ var args = _isA(msg.args) ? msg.args : [];
12337
+ fn.apply(null, args);
12338
+ return true;
12339
+ } catch (e) {
12340
+ _ce('[bw] call error:', msg.name, e);
12341
+ return false;
12342
+ }
12343
+ } else if (type === 'exec') {
12344
+ if (!bw._allowExec) {
12345
+ _cw('[bw] exec rejected: allowExec is not enabled');
12346
+ return false;
12347
+ }
12348
+ if (!msg.code) return false;
12349
+ try {
12350
+ new Function(msg.code)();
12351
+ return true;
12352
+ } catch (e) {
12353
+ _ce('[bw] exec error:', e);
12354
+ return false;
12355
+ }
12356
+ }
12357
+ return false;
12358
+ };
12359
+
12360
+ /**
12361
+ * Connect to a bwserve SSE endpoint and apply protocol messages automatically.
12362
+ *
12363
+ * Returns a connection object with sendAction(), on(), and close() methods.
12364
+ *
12365
+ * @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
12366
+ * @param {Object} [opts] - Connection options
12367
+ * @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
12368
+ * @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
12369
+ * @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
12370
+ * @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
12371
+ * @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
12372
+ * @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
12373
+ * @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
12374
+ * @returns {Object} Connection object { sendAction, on, close, status }
12375
+ * @category Server
12376
+ */
12377
+ bw.clientConnect = function (url, opts) {
12378
+ opts = opts || {};
12379
+ var transport = opts.transport || 'sse';
12380
+ var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
12381
+ var reconnect = opts.reconnect !== false;
12382
+ var onStatus = opts.onStatus || function () {};
12383
+ var onMessage = opts.onMessage || null;
12384
+ var handlers = {};
12385
+ // Set the global allowExec flag from connection options
12386
+ bw._allowExec = !!opts.allowExec;
12387
+ var conn = {
12388
+ status: 'connecting',
12389
+ _es: null,
12390
+ _pollTimer: null
12391
+ };
12392
+ function setStatus(s) {
12393
+ conn.status = s;
12394
+ onStatus(s);
12395
+ }
12396
+ function handleMessage(data) {
12397
+ try {
12398
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
12399
+ if (onMessage) onMessage(msg);
12400
+ if (handlers.message) handlers.message(msg);
12401
+ bw.clientApply(msg);
12402
+ } catch (e) {
12403
+ if (handlers.error) handlers.error(e);
12404
+ }
12405
+ }
12406
+ if (transport === 'sse' && typeof EventSource !== 'undefined') {
12407
+ setStatus('connecting');
12408
+ var es = new EventSource(url);
12409
+ conn._es = es;
12410
+ es.onopen = function () {
12411
+ setStatus('connected');
12412
+ if (handlers.open) handlers.open();
12413
+ };
12414
+ es.onmessage = function (e) {
12415
+ handleMessage(e.data);
12416
+ };
12417
+ es.onerror = function () {
12418
+ if (conn.status === 'connected') {
12419
+ setStatus('disconnected');
12420
+ }
12421
+ if (handlers.error) handlers.error(new Error('SSE connection error'));
12422
+ if (!reconnect) {
12423
+ es.close();
12424
+ }
12425
+ // EventSource auto-reconnects by default when reconnect=true
12426
+ };
12427
+ } else if (transport === 'poll') {
12428
+ var interval = opts.interval || 2000;
12429
+ setStatus('connected');
12430
+ conn._pollTimer = setInterval(function () {
12431
+ fetch(url).then(function (r) {
12432
+ return r.json();
12433
+ }).then(function (msgs) {
12434
+ if (_isA(msgs)) {
12435
+ msgs.forEach(handleMessage);
12436
+ } else if (msgs && msgs.type) {
12437
+ handleMessage(msgs);
12438
+ }
12439
+ })["catch"](function (e) {
12440
+ if (handlers.error) handlers.error(e);
12441
+ });
12442
+ }, interval);
12443
+ }
12444
+
12445
+ /**
12446
+ * Send an action to the server via POST.
12447
+ * @param {string} action - Action name
12448
+ * @param {Object} [data] - Action payload
12449
+ */
12450
+ conn.sendAction = function (action, data) {
12451
+ var body = JSON.stringify({
12452
+ type: 'action',
12453
+ action: action,
12454
+ data: data || {}
12455
+ });
12456
+ fetch(actionUrl, {
12457
+ method: 'POST',
12458
+ headers: {
12459
+ 'Content-Type': 'application/json'
12460
+ },
12461
+ body: body
12462
+ })["catch"](function (e) {
12463
+ if (handlers.error) handlers.error(e);
12464
+ });
12465
+ };
12466
+
12467
+ /**
12468
+ * Register an event handler.
12469
+ * @param {string} event - 'open'|'message'|'error'|'close'
12470
+ * @param {Function} handler
12471
+ */
12472
+ conn.on = function (event, handler) {
12473
+ handlers[event] = handler;
12474
+ return conn;
12475
+ };
12476
+
12477
+ /**
12478
+ * Close the connection.
12479
+ */
12480
+ conn.close = function () {
12481
+ if (conn._es) {
12482
+ conn._es.close();
12483
+ conn._es = null;
12484
+ }
12485
+ if (conn._pollTimer) {
12486
+ clearInterval(conn._pollTimer);
12487
+ conn._pollTimer = null;
12488
+ }
12489
+ setStatus('disconnected');
12490
+ if (handlers.close) handlers.close();
12491
+ };
12492
+ return conn;
12493
+ };
12494
+
11714
12495
  // ===================================================================================
11715
12496
  // bw.inspect() — Debug utility
11716
12497
  // ===================================================================================
@@ -11737,20 +12518,20 @@
11737
12518
  el = target.element;
11738
12519
  comp = target;
11739
12520
  } else {
11740
- if (typeof target === 'string') {
12521
+ if (_is(target, 'string')) {
11741
12522
  el = bw.$(target)[0];
11742
12523
  }
11743
12524
  if (!el) {
11744
- console.warn('bw.inspect: element not found');
12525
+ _cw('bw.inspect: element not found');
11745
12526
  return null;
11746
12527
  }
11747
12528
  comp = el._bwComponentHandle;
11748
12529
  }
11749
12530
  if (!comp) {
11750
- console.log('bw.inspect: no ComponentHandle on this element');
11751
- console.log(' Tag:', el.tagName);
11752
- console.log(' Classes:', el.className);
11753
- console.log(' _bw_state:', el._bw_state || '(none)');
12531
+ _cl('bw.inspect: no ComponentHandle on this element');
12532
+ _cl(' Tag:', el.tagName);
12533
+ _cl(' Classes:', el.className);
12534
+ _cl(' _bw_state:', el._bw_state || '(none)');
11754
12535
  return null;
11755
12536
  }
11756
12537
  var deps = comp._bindings.reduce(function (s, b) {
@@ -11759,13 +12540,13 @@
11759
12540
  return a.indexOf(v) === i;
11760
12541
  });
11761
12542
  console.group('Component: ' + comp._bwId);
11762
- console.log('State:', comp._state);
11763
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
11764
- console.log('Methods:', Object.keys(comp._methods));
11765
- console.log('Actions:', Object.keys(comp._actions));
11766
- console.log('User tag:', comp._userTag || '(none)');
11767
- console.log('Mounted:', comp.mounted);
11768
- console.log('Element:', comp.element);
12543
+ _cl('State:', comp._state);
12544
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
12545
+ _cl('Methods:', _keys(comp._methods));
12546
+ _cl('Actions:', _keys(comp._actions));
12547
+ _cl('User tag:', comp._userTag || '(none)');
12548
+ _cl('Mounted:', comp.mounted);
12549
+ _cl('Element:', comp.element);
11769
12550
  console.groupEnd();
11770
12551
  return comp;
11771
12552
  };
@@ -11788,8 +12569,8 @@
11788
12569
  // Pre-extract all binding expressions
11789
12570
  var precompiled = [];
11790
12571
  function walkExpressions(node) {
11791
- if (!node || _typeof(node) !== 'object') return;
11792
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
12572
+ if (!_is(node, 'object')) return;
12573
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
11793
12574
  var parsed = bw._parseBindings(node.c);
11794
12575
  for (var i = 0; i < parsed.length; i++) {
11795
12576
  try {
@@ -11809,9 +12590,9 @@
11809
12590
  }
11810
12591
  if (node.a) {
11811
12592
  for (var key in node.a) {
11812
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
12593
+ if (_hop.call(node.a, key)) {
11813
12594
  var v = node.a[key];
11814
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
12595
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
11815
12596
  var parsed2 = bw._parseBindings(v);
11816
12597
  for (var j = 0; j < parsed2.length; j++) {
11817
12598
  try {
@@ -11832,9 +12613,9 @@
11832
12613
  }
11833
12614
  }
11834
12615
  }
11835
- if (Array.isArray(node.c)) {
12616
+ if (_isA(node.c)) {
11836
12617
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
11837
- } else if (node.c && _typeof(node.c) === 'object' && node.c.t) {
12618
+ } else if (_is(node.c, 'object') && node.c.t) {
11838
12619
  walkExpressions(node.c);
11839
12620
  }
11840
12621
  }
@@ -11845,7 +12626,7 @@
11845
12626
  handle._precompiledBindings = precompiled;
11846
12627
  if (initialState) {
11847
12628
  for (var k in initialState) {
11848
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
12629
+ if (_hop.call(initialState, k)) {
11849
12630
  handle._state[k] = initialState[k];
11850
12631
  }
11851
12632
  }
@@ -11879,21 +12660,21 @@
11879
12660
  minify = _options$minify === void 0 ? false : _options$minify,
11880
12661
  _options$pretty = options.pretty,
11881
12662
  pretty = _options$pretty === void 0 ? !minify : _options$pretty;
11882
- if (typeof rules === 'string') return rules;
12663
+ if (_is(rules, 'string')) return rules;
11883
12664
  var css = '';
11884
12665
  var indent = pretty ? ' ' : '';
11885
12666
  var newline = pretty ? '\n' : '';
11886
12667
  var space = pretty ? ' ' : '';
11887
- if (Array.isArray(rules)) {
12668
+ if (_isA(rules)) {
11888
12669
  css = rules.map(function (rule) {
11889
12670
  return bw.css(rule, options);
11890
12671
  }).join(newline);
11891
- } else if (_typeof(rules) === 'object') {
12672
+ } else if (_is(rules, 'object')) {
11892
12673
  Object.entries(rules).forEach(function (_ref5) {
11893
12674
  var _ref6 = _slicedToArray(_ref5, 2),
11894
12675
  selector = _ref6[0],
11895
12676
  styles = _ref6[1];
11896
- if (_typeof(styles) === 'object' && !Array.isArray(styles)) {
12677
+ if (_is(styles, 'object')) {
11897
12678
  // Handle @media, @keyframes, @supports — recurse into nested block
11898
12679
  if (selector.charAt(0) === '@') {
11899
12680
  var inner = bw.css(styles, options);
@@ -11947,7 +12728,7 @@
11947
12728
  bw.injectCSS = function (css) {
11948
12729
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11949
12730
  if (!bw._isBrowser) {
11950
- console.warn('bw.injectCSS requires a DOM environment');
12731
+ _cw('bw.injectCSS requires a DOM environment');
11951
12732
  return null;
11952
12733
  }
11953
12734
  var _options$id = options.id,
@@ -11965,7 +12746,7 @@
11965
12746
  }
11966
12747
 
11967
12748
  // Convert CSS if needed
11968
- var cssStr = typeof css === 'string' ? css : bw.css(css, options);
12749
+ var cssStr = _is(css, 'string') ? css : bw.css(css, options);
11969
12750
 
11970
12751
  // Set or append CSS
11971
12752
  if (append && styleEl.textContent) {
@@ -11994,7 +12775,7 @@
11994
12775
  var result = {};
11995
12776
  for (var i = 0; i < arguments.length; i++) {
11996
12777
  var arg = arguments[i];
11997
- if (arg && _typeof(arg) === 'object') Object.assign(result, arg);
12778
+ if (_is(arg, 'object')) Object.assign(result, arg);
11998
12779
  }
11999
12780
  return result;
12000
12781
  };
@@ -12239,7 +13020,7 @@
12239
13020
  xl: '1200px'
12240
13021
  };
12241
13022
  var parts = [];
12242
- Object.keys(breakpoints).forEach(function (key) {
13023
+ _keys(breakpoints).forEach(function (key) {
12243
13024
  var rules = {};
12244
13025
  if (key === 'base') {
12245
13026
  rules[selector] = breakpoints[key];
@@ -12311,18 +13092,18 @@
12311
13092
  if (!selector) return [];
12312
13093
 
12313
13094
  // Already an array
12314
- if (Array.isArray(selector)) return selector;
13095
+ if (_isA(selector)) return selector;
12315
13096
 
12316
13097
  // Single element
12317
13098
  if (selector.nodeType) return [selector];
12318
13099
 
12319
13100
  // NodeList or HTMLCollection
12320
- if (selector.length !== undefined && typeof selector !== 'string') {
13101
+ if (selector.length !== undefined && !_is(selector, 'string')) {
12321
13102
  return Array.from(selector);
12322
13103
  }
12323
13104
 
12324
13105
  // CSS selector string
12325
- if (typeof selector === 'string') {
13106
+ if (_is(selector, 'string')) {
12326
13107
  return Array.from(document.querySelectorAll(selector));
12327
13108
  }
12328
13109
  return [];
@@ -12840,7 +13621,7 @@
12840
13621
  cls = cls.trim();
12841
13622
 
12842
13623
  // Auto-detect columns if not provided
12843
- var cols = columns || (data.length > 0 ? Object.keys(data[0]).map(function (key) {
13624
+ var cols = columns || (data.length > 0 ? _keys(data[0]).map(function (key) {
12844
13625
  return {
12845
13626
  key: key,
12846
13627
  label: key
@@ -12859,7 +13640,7 @@
12859
13640
  var bVal = b[currentSortColumn];
12860
13641
 
12861
13642
  // Handle different types
12862
- if (typeof aVal === 'number' && typeof bVal === 'number') {
13643
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
12863
13644
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
12864
13645
  }
12865
13646
 
@@ -12983,7 +13764,7 @@
12983
13764
  headerRow = _config$headerRow === void 0 ? true : _config$headerRow,
12984
13765
  columns = config.columns,
12985
13766
  rest = _objectWithoutProperties(config, _excluded);
12986
- if (!Array.isArray(data) || data.length === 0) {
13767
+ if (!_isA(data) || data.length === 0) {
12987
13768
  return bw.makeTable(_objectSpread2({
12988
13769
  data: [],
12989
13770
  columns: columns || []
@@ -13080,7 +13861,7 @@
13080
13861
  showLabels = _config$showLabels === void 0 ? true : _config$showLabels,
13081
13862
  _config$className2 = config.className,
13082
13863
  className = _config$className2 === void 0 ? '' : _config$className2;
13083
- if (!Array.isArray(data) || data.length === 0) {
13864
+ if (!_isA(data) || data.length === 0) {
13084
13865
  return {
13085
13866
  t: 'div',
13086
13867
  a: {
@@ -13263,7 +14044,7 @@
13263
14044
  bw.render = function (element, position, taco) {
13264
14045
  var _taco$o4, _taco$o5, _taco$o6;
13265
14046
  // Get target element
13266
- var targetEl = typeof element === 'string' ? document.querySelector(element) : element;
14047
+ var targetEl = _is(element, 'string') ? document.querySelector(element) : element;
13267
14048
  if (!targetEl) {
13268
14049
  return {
13269
14050
  object_type: 'error',
@@ -13401,7 +14182,7 @@
13401
14182
  setContent: function setContent(content) {
13402
14183
  this._taco.c = content;
13403
14184
  if (this.element) {
13404
- if (typeof content === 'string') {
14185
+ if (_is(content, 'string')) {
13405
14186
  this.element.textContent = content;
13406
14187
  } else {
13407
14188
  // Re-render for complex content