bitwrench 2.0.16 → 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 (42) hide show
  1. package/dist/bitwrench-bccl.cjs.js +6 -2
  2. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  3. package/dist/bitwrench-bccl.esm.js +6 -2
  4. package/dist/bitwrench-bccl.esm.min.js +3 -3
  5. package/dist/bitwrench-bccl.umd.js +6 -2
  6. package/dist/bitwrench-bccl.umd.min.js +2 -2
  7. package/dist/bitwrench-code-edit.cjs.js +1 -1
  8. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  9. package/dist/bitwrench-code-edit.es5.js +1 -1
  10. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  11. package/dist/bitwrench-code-edit.esm.js +1 -1
  12. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  13. package/dist/bitwrench-code-edit.umd.js +1 -1
  14. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  15. package/dist/bitwrench-lean.cjs.js +506 -154
  16. package/dist/bitwrench-lean.cjs.min.js +7 -7
  17. package/dist/bitwrench-lean.es5.js +517 -155
  18. package/dist/bitwrench-lean.es5.min.js +5 -5
  19. package/dist/bitwrench-lean.esm.js +505 -154
  20. package/dist/bitwrench-lean.esm.min.js +6 -6
  21. package/dist/bitwrench-lean.umd.js +506 -154
  22. package/dist/bitwrench-lean.umd.min.js +7 -7
  23. package/dist/bitwrench.cjs.js +511 -155
  24. package/dist/bitwrench.cjs.min.js +8 -8
  25. package/dist/bitwrench.es5.js +525 -156
  26. package/dist/bitwrench.es5.min.js +6 -6
  27. package/dist/bitwrench.esm.js +510 -155
  28. package/dist/bitwrench.esm.min.js +8 -8
  29. package/dist/bitwrench.umd.js +511 -155
  30. package/dist/bitwrench.umd.min.js +8 -8
  31. package/dist/builds.json +82 -82
  32. package/dist/bwserve.cjs.js +16 -2
  33. package/dist/bwserve.esm.js +16 -2
  34. package/dist/sri.json +34 -34
  35. package/package.json +4 -2
  36. package/readme.html +1 -1
  37. package/src/bitwrench-bccl.js +5 -1
  38. package/src/bitwrench.js +502 -151
  39. package/src/bwserve/index.js +12 -1
  40. package/src/bwserve/shell.js +3 -0
  41. package/src/cli/layout-default.js +47 -32
  42. package/src/version.js +3 -3
@@ -1,18 +1,18 @@
1
- /*! bitwrench-lean v2.0.16 | 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
  /**
3
3
  * Auto-generated version file from package.json
4
4
  * DO NOT EDIT DIRECTLY - Use npm run generate-version
5
5
  */
6
6
 
7
7
  const VERSION_INFO = {
8
- version: '2.0.16',
8
+ version: '2.0.17',
9
9
  name: 'bitwrench',
10
10
  description: 'A library for javascript UI functions.',
11
11
  license: 'BSD-2-Clause',
12
12
  homepage: 'https://deftio.github.com/bitwrench/pages',
13
13
  repository: 'git+https://github.com/deftio/bitwrench.git',
14
14
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
15
- buildDate: '2026-03-12T08:05:52.043Z'
15
+ buildDate: '2026-03-13T23:15:10.823Z'
16
16
  };
17
17
 
18
18
  /**
@@ -3343,7 +3343,7 @@ const bw = {
3343
3343
  __monkey_patch_is_nodejs__: {
3344
3344
  _value: 'ignore',
3345
3345
  set: function(x) {
3346
- this._value = (typeof x === 'boolean') ? x : 'ignore';
3346
+ this._value = _is(x, 'boolean') ? x : 'ignore';
3347
3347
  },
3348
3348
  get: function() {
3349
3349
  return this._value;
@@ -3391,6 +3391,67 @@ Object.defineProperty(bw, '_isBrowser', {
3391
3391
  configurable: true
3392
3392
  });
3393
3393
 
3394
+ // ── Internal aliases ─────────────────────────────────────────────────────
3395
+ // Short names for frequently-used builtins and internal methods.
3396
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
3397
+ //
3398
+ // Why: Terser can't shorten global property chains (console.warn,
3399
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
3400
+ // because it can't prove they're side-effect-free. We can, so we alias
3401
+ // them here. Each alias saves bytes in the minified output, and the short
3402
+ // names also reduce visual noise in the hot paths (binding pipeline,
3403
+ // createDOM, etc.).
3404
+ //
3405
+ // Alias Target Sites
3406
+ // ───────── ────────────────────────────────────── ─────
3407
+ // _hop Object.prototype.hasOwnProperty 15
3408
+ // _isA Array.isArray 25
3409
+ // _keys Object.keys 7
3410
+ // _to bw.typeOf (type string) 26
3411
+ // _is type check boolean: _is(x,'string') ~50
3412
+ // _cw console.warn 8
3413
+ // _cl console.log 11
3414
+ // _ce console.error 4
3415
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
3416
+ //
3417
+ // Note: document.createElement etc. are NOT aliased because they require
3418
+ // `this === document` and .bind() would add overhead on every call.
3419
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
3420
+ // patching of console.warn/log/error continues to work.
3421
+ //
3422
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
3423
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
3424
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
3425
+ //
3426
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
3427
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
3428
+ // returns trueVal if _to(x)===type, else falseVal.
3429
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
3430
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
3431
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
3432
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
3433
+ // Uncomment if pattern frequency justifies them:
3434
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
3435
+ // 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); };
3436
+ // ─────────────────────────────────────────────────────────────────────────
3437
+ var _hop = Object.prototype.hasOwnProperty;
3438
+ var _isA = Array.isArray;
3439
+ var _keys = Object.keys;
3440
+ var _to = typeOf; // imported from bitwrench-utils.js
3441
+ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
3442
+ // Console aliases use thin wrappers (not direct references) so that test
3443
+ // code can monkey-patch console.warn/log/error and the patches take effect.
3444
+ var _cw = function() { console.warn.apply(console, arguments); };
3445
+ var _cl = function() { console.log.apply(console, arguments); };
3446
+ var _ce = function() { console.error.apply(console, arguments); };
3447
+
3448
+ /**
3449
+ * Debug flag. When true, emits console.warn for silent binding failures
3450
+ * (missing paths, null refs, auto-created intermediate objects).
3451
+ * @type {boolean}
3452
+ */
3453
+ bw.debug = false;
3454
+
3394
3455
  /**
3395
3456
  * Lazy-resolve Node.js `fs` module.
3396
3457
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -3538,7 +3599,7 @@ bw.uuid = function(prefix) {
3538
3599
  */
3539
3600
  bw._el = function(id) {
3540
3601
  // Pass-through for DOM elements
3541
- if (typeof id !== 'string') return id || null;
3602
+ if (!_is(id, 'string')) return id || null;
3542
3603
  if (!id) return null;
3543
3604
  if (!bw._isBrowser) return null;
3544
3605
 
@@ -3634,7 +3695,7 @@ bw._deregisterNode = function(el, bwId) {
3634
3695
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
3635
3696
  */
3636
3697
  bw.escapeHTML = function(str) {
3637
- if (typeof str !== 'string') return '';
3698
+ if (!_is(str, 'string')) return '';
3638
3699
 
3639
3700
  const escapeMap = {
3640
3701
  '&': '&amp;',
@@ -3707,7 +3768,7 @@ bw.html = function(taco, options = {}) {
3707
3768
  }
3708
3769
 
3709
3770
  // Handle arrays of TACOs
3710
- if (Array.isArray(taco)) {
3771
+ if (_isA(taco)) {
3711
3772
  return taco.map(t => bw.html(t, options)).join('');
3712
3773
  }
3713
3774
 
@@ -3730,15 +3791,15 @@ bw.html = function(taco, options = {}) {
3730
3791
  if (taco && taco._bwEach && options.state) {
3731
3792
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
3732
3793
  var arr = bw._evaluatePath(options.state, eachExpr);
3733
- if (!Array.isArray(arr)) return '';
3794
+ if (!_isA(arr)) return '';
3734
3795
  return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
3735
3796
  }
3736
3797
 
3737
3798
  // Handle primitives and non-TACO objects
3738
- if (typeof taco !== 'object' || !taco.t) {
3799
+ if (!_is(taco, 'object') || !taco.t) {
3739
3800
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
3740
3801
  // Resolve template bindings if state provided
3741
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
3802
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
3742
3803
  str = bw._resolveTemplate(str, options.state, !!options.compile);
3743
3804
  }
3744
3805
  return str;
@@ -3758,10 +3819,18 @@ bw.html = function(taco, options = {}) {
3758
3819
  // Skip null, undefined, false
3759
3820
  if (value == null || value === false) continue;
3760
3821
 
3761
- // Skip event handlers (they're for DOM only)
3762
- if (key.startsWith('on')) continue;
3822
+ // Serialize event handlers via funcRegister
3823
+ if (key.startsWith('on')) {
3824
+ if (_is(value, 'function')) {
3825
+ var fnId = bw.funcRegister(value);
3826
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
3827
+ } else if (_is(value, 'string')) {
3828
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
3829
+ }
3830
+ continue;
3831
+ }
3763
3832
 
3764
- if (key === 'style' && typeof value === 'object') {
3833
+ if (key === 'style' && _is(value, 'object')) {
3765
3834
  // Convert style object to string
3766
3835
  const styleStr = Object.entries(value)
3767
3836
  .filter(([, v]) => v != null)
@@ -3772,7 +3841,7 @@ bw.html = function(taco, options = {}) {
3772
3841
  }
3773
3842
  } else if (key === 'class') {
3774
3843
  // Handle class as array or string
3775
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
3844
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
3776
3845
  if (classStr) {
3777
3846
  attrStr += ` class="${bw.escapeHTML(classStr)}"`;
3778
3847
  }
@@ -3808,13 +3877,184 @@ bw.html = function(taco, options = {}) {
3808
3877
  // Process content recursively
3809
3878
  let contentStr = content != null ? bw.html(content, options) : '';
3810
3879
  // Resolve template bindings in content if state provided
3811
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
3880
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
3812
3881
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
3813
3882
  }
3814
3883
 
3815
3884
  return `<${tag}${attrStr}>${contentStr}</${tag}>`;
3816
3885
  };
3817
3886
 
3887
+ /**
3888
+ * Generate a complete, self-contained HTML document from TACO content.
3889
+ *
3890
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
3891
+ * func registry emission (so serialized event handlers work), optional theme,
3892
+ * and extra head elements. Designed for static site generation, offline/airgapped
3893
+ * use, and the "static site that isn't static" workflow.
3894
+ *
3895
+ * @param {Object} [opts={}] - Page options
3896
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
3897
+ * @param {string} [opts.title='bitwrench'] - Page title
3898
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
3899
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
3900
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
3901
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
3902
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
3903
+ * @param {string} [opts.favicon=''] - Favicon URL
3904
+ * @param {string} [opts.lang='en'] - HTML lang attribute
3905
+ * @returns {string} Complete HTML document string
3906
+ * @category DOM Generation
3907
+ * @see bw.html
3908
+ * @example
3909
+ * bw.htmlPage({
3910
+ * title: 'My App',
3911
+ * body: { t: 'h1', c: 'Hello World' },
3912
+ * runtime: 'shim'
3913
+ * })
3914
+ */
3915
+ bw.htmlPage = function(opts) {
3916
+ opts = opts || {};
3917
+ var title = opts.title || 'bitwrench';
3918
+ var body = opts.body || '';
3919
+ var state = opts.state || undefined;
3920
+ var runtime = opts.runtime || 'shim';
3921
+ var css = opts.css || '';
3922
+ var theme = opts.theme || null;
3923
+ var headExtra = opts.head || [];
3924
+ var favicon = opts.favicon || '';
3925
+ var lang = opts.lang || 'en';
3926
+
3927
+ // Snapshot funcRegistry counter before rendering
3928
+ var fnCounterBefore = bw._fnIDCounter;
3929
+
3930
+ // Render body content
3931
+ var bodyHTML = '';
3932
+ if (_is(body, 'string')) {
3933
+ bodyHTML = body;
3934
+ } else {
3935
+ var htmlOpts = {};
3936
+ if (state) htmlOpts.state = state;
3937
+ bodyHTML = bw.html(body, htmlOpts);
3938
+ }
3939
+
3940
+ // Collect functions registered during this render
3941
+ var fnCounterAfter = bw._fnIDCounter;
3942
+ var registryEntries = '';
3943
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
3944
+ var fnKey = 'bw_fn_' + i;
3945
+ if (bw._fnRegistry[fnKey]) {
3946
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
3947
+ bw._fnRegistry[fnKey].toString() + ';\n';
3948
+ }
3949
+ }
3950
+
3951
+ // Build runtime script for <head>
3952
+ var runtimeHead = '';
3953
+ if (runtime === 'inline') {
3954
+ // Read UMD bundle synchronously if in Node.js
3955
+ var umdSource = null;
3956
+ if (bw._isNode) {
3957
+ try {
3958
+ var fs = (typeof require === 'function') ? require('fs') : null;
3959
+ var pathMod = (typeof require === 'function') ? require('path') : null;
3960
+ if (fs && pathMod) {
3961
+ // Resolve dist/ relative to this source file
3962
+ var srcDir = '';
3963
+ try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
3964
+ catch(e2) { /* ESM: __filename not available */ }
3965
+ if (!srcDir && typeof import.meta !== 'undefined' && import.meta.url) {
3966
+ var url = (typeof require === 'function') ? require('url') : null;
3967
+ if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath(import.meta.url));
3968
+ }
3969
+ if (srcDir) {
3970
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
3971
+ umdSource = fs.readFileSync(distPath, 'utf8');
3972
+ }
3973
+ }
3974
+ } catch(e) { /* fall through */ }
3975
+ }
3976
+ if (umdSource) {
3977
+ runtimeHead = '<script>' + umdSource + '</script>';
3978
+ } else {
3979
+ // Fallback to shim in browser or if dist not available
3980
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
3981
+ }
3982
+ } else if (runtime === 'cdn') {
3983
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
3984
+ } else if (runtime === 'shim') {
3985
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
3986
+ }
3987
+ // runtime === 'none' → empty
3988
+
3989
+ // Theme CSS
3990
+ var themeCSS = '';
3991
+ if (theme) {
3992
+ var themeConfig = _is(theme, 'string')
3993
+ ? (THEME_PRESETS[theme.toLowerCase()] || null)
3994
+ : theme;
3995
+ if (themeConfig) {
3996
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
3997
+ themeCSS = themeResult.css;
3998
+ }
3999
+ }
4000
+
4001
+ // Extra <head> elements
4002
+ var headHTML = '';
4003
+ if (_isA(headExtra) && headExtra.length > 0) {
4004
+ headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
4005
+ }
4006
+
4007
+ // Favicon
4008
+ var faviconTag = '';
4009
+ if (favicon) {
4010
+ var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
4011
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c];
4012
+ });
4013
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
4014
+ }
4015
+
4016
+ // Escaped title
4017
+ var safeTitle = bw.escapeHTML(title);
4018
+
4019
+ // Combine all CSS
4020
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
4021
+
4022
+ // Body-end script: registry entries + optional loadDefaultStyles
4023
+ var bodyEndScript = '';
4024
+ var bodyEndParts = [];
4025
+ if (registryEntries) {
4026
+ bodyEndParts.push(registryEntries);
4027
+ }
4028
+ if (runtime === 'inline' || runtime === 'cdn') {
4029
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
4030
+ }
4031
+ if (bodyEndParts.length > 0) {
4032
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
4033
+ }
4034
+
4035
+ // Assemble document
4036
+ var parts = [
4037
+ '<!DOCTYPE html>',
4038
+ '<html lang="' + lang + '">',
4039
+ '<head>',
4040
+ '<meta charset="UTF-8">',
4041
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
4042
+ ];
4043
+ parts.push('<title>' + safeTitle + '</title>');
4044
+ if (faviconTag) parts.push(faviconTag);
4045
+ if (runtimeHead) parts.push(runtimeHead);
4046
+ if (headHTML) parts.push(headHTML);
4047
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
4048
+ parts.push('</head>');
4049
+ parts.push('<body>');
4050
+ parts.push(bodyHTML);
4051
+ if (bodyEndScript) parts.push(bodyEndScript);
4052
+ parts.push('</body>');
4053
+ parts.push('</html>');
4054
+
4055
+ return parts.join('\n');
4056
+ };
4057
+
3818
4058
  /**
3819
4059
  * Create a live DOM element from a TACO object (browser only).
3820
4060
  *
@@ -3859,7 +4099,7 @@ bw.createDOM = function(taco, options = {}) {
3859
4099
  }
3860
4100
 
3861
4101
  // Handle text nodes
3862
- if (typeof taco !== 'object' || !taco.t) {
4102
+ if (!_is(taco, 'object') || !taco.t) {
3863
4103
  return document.createTextNode(String(taco));
3864
4104
  }
3865
4105
 
@@ -3872,16 +4112,16 @@ bw.createDOM = function(taco, options = {}) {
3872
4112
  for (const [key, value] of Object.entries(attrs)) {
3873
4113
  if (value == null || value === false) continue;
3874
4114
 
3875
- if (key === 'style' && typeof value === 'object') {
4115
+ if (key === 'style' && _is(value, 'object')) {
3876
4116
  // Apply styles directly
3877
4117
  Object.assign(el.style, value);
3878
4118
  } else if (key === 'class') {
3879
4119
  // Handle class as array or string
3880
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
4120
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
3881
4121
  if (classStr) {
3882
4122
  el.className = classStr;
3883
4123
  }
3884
- } else if (key.startsWith('on') && typeof value === 'function') {
4124
+ } else if (key.startsWith('on') && _is(value, 'function')) {
3885
4125
  // Event handlers
3886
4126
  const eventName = key.slice(2).toLowerCase();
3887
4127
  el.addEventListener(eventName, value);
@@ -3901,7 +4141,7 @@ bw.createDOM = function(taco, options = {}) {
3901
4141
  // Children with data-bw_id or id attributes get local refs on the parent,
3902
4142
  // so o.render functions can access them without any DOM lookup.
3903
4143
  if (content != null) {
3904
- if (Array.isArray(content)) {
4144
+ if (_isA(content)) {
3905
4145
  content.forEach(child => {
3906
4146
  if (child != null) {
3907
4147
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -3921,20 +4161,20 @@ bw.createDOM = function(taco, options = {}) {
3921
4161
  if (childEl._bw_refs) {
3922
4162
  if (!el._bw_refs) el._bw_refs = {};
3923
4163
  for (var rk in childEl._bw_refs) {
3924
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
4164
+ if (_hop.call(childEl._bw_refs, rk)) {
3925
4165
  el._bw_refs[rk] = childEl._bw_refs[rk];
3926
4166
  }
3927
4167
  }
3928
4168
  }
3929
4169
  }
3930
4170
  });
3931
- } else if (typeof content === 'object' && content.__bw_raw) {
4171
+ } else if (_is(content, 'object') && content.__bw_raw) {
3932
4172
  // Raw HTML content — inject via innerHTML
3933
4173
  el.innerHTML = content.v;
3934
4174
  } else if (content._bwComponent === true) {
3935
4175
  // Single ComponentHandle as content
3936
4176
  content.mount(el);
3937
- } else if (typeof content === 'object' && content.t) {
4177
+ } else if (_is(content, 'object') && content.t) {
3938
4178
  var childEl = bw.createDOM(content, options);
3939
4179
  el.appendChild(childEl);
3940
4180
  var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
@@ -3945,7 +4185,7 @@ bw.createDOM = function(taco, options = {}) {
3945
4185
  if (childEl._bw_refs) {
3946
4186
  if (!el._bw_refs) el._bw_refs = {};
3947
4187
  for (var rk in childEl._bw_refs) {
3948
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
4188
+ if (_hop.call(childEl._bw_refs, rk)) {
3949
4189
  el._bw_refs[rk] = childEl._bw_refs[rk];
3950
4190
  }
3951
4191
  }
@@ -3978,7 +4218,7 @@ bw.createDOM = function(taco, options = {}) {
3978
4218
  el._bw_render = opts.render;
3979
4219
 
3980
4220
  if (opts.mounted) {
3981
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
4221
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
3982
4222
  }
3983
4223
 
3984
4224
  // Queue initial render (same timing as mounted)
@@ -4051,7 +4291,7 @@ bw.DOM = function(target, taco, options = {}) {
4051
4291
  const targetEl = bw._el(target);
4052
4292
 
4053
4293
  if (!targetEl) {
4054
- console.error('bw.DOM: Target element not found:', target);
4294
+ _ce('bw.DOM: Target element not found:', target);
4055
4295
  return null;
4056
4296
  }
4057
4297
 
@@ -4091,7 +4331,7 @@ bw.DOM = function(target, taco, options = {}) {
4091
4331
  targetEl.appendChild(taco.element);
4092
4332
  }
4093
4333
  // Handle arrays
4094
- else if (Array.isArray(taco)) {
4334
+ else if (_isA(taco)) {
4095
4335
  taco.forEach(t => {
4096
4336
  if (t != null) {
4097
4337
  if (t._bwComponent === true) {
@@ -4127,7 +4367,7 @@ bw.DOM = function(target, taco, options = {}) {
4127
4367
  bw.compileProps = function(handle, props = {}) {
4128
4368
  const compiledProps = {};
4129
4369
 
4130
- Object.keys(props).forEach(key => {
4370
+ _keys(props).forEach(key => {
4131
4371
  // Create getter/setter for each prop
4132
4372
  Object.defineProperty(compiledProps, key, {
4133
4373
  get() {
@@ -4445,17 +4685,17 @@ bw.patch = function(id, content, attr) {
4445
4685
  if (attr) {
4446
4686
  // Patch an attribute
4447
4687
  el.setAttribute(attr, String(content));
4448
- } else if (Array.isArray(content)) {
4688
+ } else if (_isA(content)) {
4449
4689
  // Patch with array of children (strings and/or TACOs)
4450
4690
  el.innerHTML = '';
4451
4691
  content.forEach(function(item) {
4452
- if (typeof item === 'string' || typeof item === 'number') {
4692
+ if (_is(item, 'string') || _is(item, 'number')) {
4453
4693
  el.appendChild(document.createTextNode(String(item)));
4454
4694
  } else if (item && item.t) {
4455
4695
  el.appendChild(bw.createDOM(item));
4456
4696
  }
4457
4697
  });
4458
- } else if (typeof content === 'object' && content !== null && content.t) {
4698
+ } else if (_is(content, 'object') && content.t) {
4459
4699
  // Patch with a TACO — replace children
4460
4700
  el.innerHTML = '';
4461
4701
  el.appendChild(bw.createDOM(content));
@@ -4486,7 +4726,7 @@ bw.patch = function(id, content, attr) {
4486
4726
  bw.patchAll = function(patches) {
4487
4727
  var results = {};
4488
4728
  for (var id in patches) {
4489
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
4729
+ if (_hop.call(patches, id)) {
4490
4730
  results[id] = bw.patch(id, patches[id]);
4491
4731
  }
4492
4732
  }
@@ -4583,7 +4823,7 @@ bw.pub = function(topic, detail) {
4583
4823
  snapshot[i].handler(detail);
4584
4824
  called++;
4585
4825
  } catch (err) {
4586
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
4826
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
4587
4827
  }
4588
4828
  }
4589
4829
  return called;
@@ -4679,8 +4919,8 @@ bw._fnIDCounter = 0;
4679
4919
  * @see bw.funcGetDispatchStr
4680
4920
  */
4681
4921
  bw.funcRegister = function(fn, name) {
4682
- if (typeof fn !== 'function') return '';
4683
- var fnID = (typeof name === 'string' && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
4922
+ if (!_is(fn, 'function')) return '';
4923
+ var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
4684
4924
  bw._fnRegistry[fnID] = fn;
4685
4925
  return fnID;
4686
4926
  };
@@ -4699,7 +4939,7 @@ bw.funcRegister = function(fn, name) {
4699
4939
  bw.funcGetById = function(name, errFn) {
4700
4940
  name = String(name);
4701
4941
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
4702
- return (typeof errFn === 'function') ? errFn : function() { console.warn('bw.funcGetById: unregistered fn "' + name + '"'); };
4942
+ return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
4703
4943
  };
4704
4944
 
4705
4945
  /**
@@ -4740,13 +4980,30 @@ bw.funcUnregister = function(name) {
4740
4980
  bw.funcGetRegistry = function() {
4741
4981
  var copy = {};
4742
4982
  for (var k in bw._fnRegistry) {
4743
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
4983
+ if (_hop.call(bw._fnRegistry, k)) {
4744
4984
  copy[k] = bw._fnRegistry[k];
4745
4985
  }
4746
4986
  }
4747
4987
  return copy;
4748
4988
  };
4749
4989
 
4990
+ /**
4991
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
4992
+ * When embedded in a `<script>` tag, provides just enough infrastructure
4993
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
4994
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
4995
+ * @type {string}
4996
+ * @category Function Registry
4997
+ */
4998
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
4999
+ 'if(!bw._fnRegistry)bw._fnRegistry={};' +
5000
+ 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
5001
+ 'console.warn("bw: unregistered fn "+n)};};' +
5002
+ 'bw.funcRegister=function(fn,name){' +
5003
+ 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
5004
+ 'bw._fnRegistry[id]=fn;return id;};' +
5005
+ 'window.bw=bw;})();';
5006
+
4750
5007
  // ===================================================================================
4751
5008
  // Template Binding Utilities
4752
5009
  // ===================================================================================
@@ -4774,7 +5031,10 @@ bw._evaluatePath = function(state, path) {
4774
5031
  var parts = path.split('.');
4775
5032
  var val = state;
4776
5033
  for (var i = 0; i < parts.length; i++) {
4777
- if (val == null) return '';
5034
+ if (val == null) {
5035
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
5036
+ return '';
5037
+ }
4778
5038
  val = val[parts[i]];
4779
5039
  }
4780
5040
  return (val == null) ? '' : val;
@@ -4794,7 +5054,7 @@ bw._evaluatePath = function(state, path) {
4794
5054
  */
4795
5055
  bw._compiledExprs = {};
4796
5056
  bw._resolveTemplate = function(str, state, compile) {
4797
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
5057
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
4798
5058
  var bindings = bw._parseBindings(str);
4799
5059
  if (bindings.length === 0) return str;
4800
5060
 
@@ -4816,6 +5076,7 @@ bw._resolveTemplate = function(str, state, compile) {
4816
5076
  try {
4817
5077
  val = bw._compiledExprs[b.expr](state);
4818
5078
  } catch (e) {
5079
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
4819
5080
  val = '';
4820
5081
  }
4821
5082
  } else {
@@ -4924,7 +5185,7 @@ function ComponentHandle(taco) {
4924
5185
  this._state = {};
4925
5186
  if (o.state) {
4926
5187
  for (var k in o.state) {
4927
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
5188
+ if (_hop.call(o.state, k)) {
4928
5189
  this._state[k] = o.state[k];
4929
5190
  }
4930
5191
  }
@@ -4933,7 +5194,7 @@ function ComponentHandle(taco) {
4933
5194
  this._actions = {};
4934
5195
  if (o.actions) {
4935
5196
  for (var k2 in o.actions) {
4936
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
5197
+ if (_hop.call(o.actions, k2)) {
4937
5198
  this._actions[k2] = o.actions[k2];
4938
5199
  }
4939
5200
  }
@@ -4943,7 +5204,7 @@ function ComponentHandle(taco) {
4943
5204
  if (o.methods) {
4944
5205
  var self = this;
4945
5206
  for (var k3 in o.methods) {
4946
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
5207
+ if (_hop.call(o.methods, k3)) {
4947
5208
  this._methods[k3] = o.methods[k3];
4948
5209
  (function(methodName, methodFn) {
4949
5210
  self[methodName] = function() {
@@ -4976,14 +5237,23 @@ function ComponentHandle(taco) {
4976
5237
  this._compile = !!o.compile;
4977
5238
  this._bw_refs = {};
4978
5239
  this._refCounter = 0;
5240
+ // Child component ownership (Bug #5)
5241
+ this._children = [];
5242
+ this._parent = null;
5243
+ // Factory metadata for BCCL rebuild (Bug #6)
5244
+ this._factory = taco._bwFactory || null;
4979
5245
  }
4980
5246
 
5247
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
5248
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
5249
+ var _chp = ComponentHandle.prototype;
5250
+
4981
5251
  // ── State Methods ──
4982
5252
 
4983
5253
  /**
4984
5254
  * Get a state value. Dot-path supported: `get('user.name')`
4985
5255
  */
4986
- ComponentHandle.prototype.get = function(key) {
5256
+ _chp.get = function(key) {
4987
5257
  return bw._evaluatePath(this._state, key);
4988
5258
  };
4989
5259
 
@@ -4993,12 +5263,13 @@ ComponentHandle.prototype.get = function(key) {
4993
5263
  * @param {*} value - New value
4994
5264
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
4995
5265
  */
4996
- ComponentHandle.prototype.set = function(key, value, opts) {
5266
+ _chp.set = function(key, value, opts) {
4997
5267
  // Dot-path set
4998
5268
  var parts = key.split('.');
4999
5269
  var obj = this._state;
5000
5270
  for (var i = 0; i < parts.length - 1; i++) {
5001
- if (obj[parts[i]] == null || typeof obj[parts[i]] !== 'object') {
5271
+ if (!_is(obj[parts[i]], 'object')) {
5272
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
5002
5273
  obj[parts[i]] = {};
5003
5274
  }
5004
5275
  obj = obj[parts[i]];
@@ -5018,10 +5289,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
5018
5289
  /**
5019
5290
  * Get a shallow clone of the full state.
5020
5291
  */
5021
- ComponentHandle.prototype.getState = function() {
5292
+ _chp.getState = function() {
5022
5293
  var clone = {};
5023
5294
  for (var k in this._state) {
5024
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
5295
+ if (_hop.call(this._state, k)) {
5025
5296
  clone[k] = this._state[k];
5026
5297
  }
5027
5298
  }
@@ -5033,9 +5304,9 @@ ComponentHandle.prototype.getState = function() {
5033
5304
  * @param {Object} updates - Key-value pairs to merge
5034
5305
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
5035
5306
  */
5036
- ComponentHandle.prototype.setState = function(updates, opts) {
5307
+ _chp.setState = function(updates, opts) {
5037
5308
  for (var k in updates) {
5038
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
5309
+ if (_hop.call(updates, k)) {
5039
5310
  this._state[k] = updates[k];
5040
5311
  this._dirtyKeys[k] = true;
5041
5312
  }
@@ -5052,9 +5323,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
5052
5323
  /**
5053
5324
  * Push a value onto an array in state. Clones the array.
5054
5325
  */
5055
- ComponentHandle.prototype.push = function(key, val) {
5326
+ _chp.push = function(key, val) {
5056
5327
  var arr = this.get(key);
5057
- var newArr = Array.isArray(arr) ? arr.slice() : [];
5328
+ var newArr = _isA(arr) ? arr.slice() : [];
5058
5329
  newArr.push(val);
5059
5330
  this.set(key, newArr);
5060
5331
  };
@@ -5062,9 +5333,9 @@ ComponentHandle.prototype.push = function(key, val) {
5062
5333
  /**
5063
5334
  * Splice an array in state. Clones the array.
5064
5335
  */
5065
- ComponentHandle.prototype.splice = function(key, start, deleteCount) {
5336
+ _chp.splice = function(key, start, deleteCount) {
5066
5337
  var arr = this.get(key);
5067
- var newArr = Array.isArray(arr) ? arr.slice() : [];
5338
+ var newArr = _isA(arr) ? arr.slice() : [];
5068
5339
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
5069
5340
  Array.prototype.splice.apply(newArr, args);
5070
5341
  this.set(key, newArr);
@@ -5072,7 +5343,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
5072
5343
 
5073
5344
  // ── Scheduling ──
5074
5345
 
5075
- ComponentHandle.prototype._scheduleDirty = function() {
5346
+ _chp._scheduleDirty = function() {
5076
5347
  if (!this._scheduled) {
5077
5348
  this._scheduled = true;
5078
5349
  bw._dirtyComponents.push(this);
@@ -5087,17 +5358,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
5087
5358
  * Creates binding descriptors with refIds for targeted DOM updates.
5088
5359
  * @private
5089
5360
  */
5090
- ComponentHandle.prototype._compileBindings = function() {
5361
+ _chp._compileBindings = function() {
5091
5362
  this._bindings = [];
5092
5363
  this._refCounter = 0;
5093
- var stateKeys = Object.keys(this._state);
5364
+ var stateKeys = _keys(this._state);
5094
5365
  var self = this;
5095
5366
 
5096
5367
  function walkTaco(taco, path) {
5097
- if (taco == null || typeof taco !== 'object' || !taco.t) return taco;
5368
+ if (!_is(taco, 'object') || !taco.t) return taco;
5098
5369
 
5099
5370
  // Check content for bindings
5100
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
5371
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
5101
5372
  var refId = 'bw_ref_' + self._refCounter++;
5102
5373
  var parsed = bw._parseBindings(taco.c);
5103
5374
  var deps = [];
@@ -5119,10 +5390,10 @@ ComponentHandle.prototype._compileBindings = function() {
5119
5390
  // Check attributes for bindings
5120
5391
  if (taco.a) {
5121
5392
  for (var attrName in taco.a) {
5122
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
5393
+ if (!_hop.call(taco.a, attrName)) continue;
5123
5394
  if (attrName === 'data-bw_ref') continue;
5124
5395
  var attrVal = taco.a[attrName];
5125
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
5396
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
5126
5397
  var refId2 = 'bw_ref_' + self._refCounter++;
5127
5398
  var parsed2 = bw._parseBindings(attrVal);
5128
5399
  var deps2 = [];
@@ -5148,9 +5419,27 @@ ComponentHandle.prototype._compileBindings = function() {
5148
5419
  }
5149
5420
 
5150
5421
  // Recurse into children
5151
- if (Array.isArray(taco.c)) {
5422
+ if (_isA(taco.c)) {
5152
5423
  for (var i = 0; i < taco.c.length; i++) {
5153
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
5424
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
5425
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
5426
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
5427
+ var mixedParsed = bw._parseBindings(taco.c[i]);
5428
+ var mixedDeps = [];
5429
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
5430
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
5431
+ }
5432
+ self._bindings.push({
5433
+ expr: taco.c[i],
5434
+ type: 'content',
5435
+ refId: mixedRefId,
5436
+ deps: mixedDeps,
5437
+ template: taco.c[i]
5438
+ });
5439
+ // Replace string with a span wrapper so textContent targets the span only
5440
+ taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
5441
+ }
5442
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
5154
5443
  walkTaco(taco.c[i], path.concat(i));
5155
5444
  }
5156
5445
  // Handle bw.when/bw.each markers
@@ -5185,7 +5474,7 @@ ComponentHandle.prototype._compileBindings = function() {
5185
5474
  taco.c[i]._refId = eachRefId;
5186
5475
  }
5187
5476
  }
5188
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
5477
+ } else if (_is(taco.c, 'object') && taco.c.t) {
5189
5478
  walkTaco(taco.c, path.concat(0));
5190
5479
  }
5191
5480
 
@@ -5201,7 +5490,7 @@ ComponentHandle.prototype._compileBindings = function() {
5201
5490
  * Build ref map from the live DOM after createDOM.
5202
5491
  * @private
5203
5492
  */
5204
- ComponentHandle.prototype._collectRefs = function() {
5493
+ _chp._collectRefs = function() {
5205
5494
  this._bw_refs = {};
5206
5495
  if (!this.element) return;
5207
5496
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -5222,7 +5511,7 @@ ComponentHandle.prototype._collectRefs = function() {
5222
5511
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
5223
5512
  * @param {Element} parentEl - DOM element to mount into
5224
5513
  */
5225
- ComponentHandle.prototype.mount = function(parentEl) {
5514
+ _chp.mount = function(parentEl) {
5226
5515
  // willMount hook
5227
5516
  if (this._hooks.willMount) this._hooks.willMount(this);
5228
5517
 
@@ -5244,7 +5533,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
5244
5533
  // Register named actions in function registry
5245
5534
  var self = this;
5246
5535
  for (var actionName in this._actions) {
5247
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
5536
+ if (_hop.call(this._actions, actionName)) {
5248
5537
  var registeredName = this._bwId + '_' + actionName;
5249
5538
  (function(aName) {
5250
5539
  bw.funcRegister(function(evt) {
@@ -5263,6 +5552,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
5263
5552
  this.element = bw.createDOM(tacoForDOM);
5264
5553
  this.element._bwComponentHandle = this;
5265
5554
  this.element.setAttribute('data-bw_comp_id', this._bwId);
5555
+
5556
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
5557
+ if (this.taco.o && this.taco.o.render) {
5558
+ this.element._bw_render = this.taco.o.render;
5559
+ }
5266
5560
  if (this._userTag) {
5267
5561
  this.element.classList.add(this._userTag);
5268
5562
  }
@@ -5278,6 +5572,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
5278
5572
 
5279
5573
  this.mounted = true;
5280
5574
 
5575
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
5576
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
5577
+ for (var ci = 0; ci < childEls.length; ci++) {
5578
+ var ch = childEls[ci]._bwComponentHandle;
5579
+ if (ch && ch !== this && !ch._parent) {
5580
+ ch._parent = this;
5581
+ this._children.push(ch);
5582
+ }
5583
+ }
5584
+
5281
5585
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
5282
5586
  if (this._hooks.mounted) {
5283
5587
  if (this._hooks.mounted.length === 2) {
@@ -5286,16 +5590,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
5286
5590
  this._hooks.mounted(this);
5287
5591
  }
5288
5592
  }
5593
+
5594
+ // Invoke o.render on initial mount (if present)
5595
+ if (this.element._bw_render) {
5596
+ this.element._bw_render(this.element, this._state);
5597
+ }
5289
5598
  };
5290
5599
 
5291
5600
  /**
5292
5601
  * Prepare TACO for initial render: resolve when/each markers.
5293
5602
  * @private
5294
5603
  */
5295
- ComponentHandle.prototype._prepareTaco = function(taco) {
5296
- if (!taco || typeof taco !== 'object') return;
5604
+ _chp._prepareTaco = function(taco) {
5605
+ if (!_is(taco, 'object')) return;
5297
5606
 
5298
- if (Array.isArray(taco.c)) {
5607
+ if (_isA(taco.c)) {
5299
5608
  for (var i = taco.c.length - 1; i >= 0; i--) {
5300
5609
  var child = taco.c[i];
5301
5610
  if (child && child._bwWhen) {
@@ -5320,18 +5629,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
5320
5629
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
5321
5630
  var arr = bw._evaluatePath(this._state, eachExprStr);
5322
5631
  var items = [];
5323
- if (Array.isArray(arr)) {
5632
+ if (_isA(arr)) {
5324
5633
  for (var j = 0; j < arr.length; j++) {
5325
5634
  items.push(child.factory(arr[j], j));
5326
5635
  }
5327
5636
  }
5328
5637
  taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
5329
5638
  }
5330
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
5639
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
5331
5640
  this._prepareTaco(taco.c[i]);
5332
5641
  }
5333
5642
  }
5334
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
5643
+ } else if (_is(taco.c, 'object') && taco.c.t) {
5335
5644
  this._prepareTaco(taco.c);
5336
5645
  }
5337
5646
  };
@@ -5340,12 +5649,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
5340
5649
  * Wire action name strings (in onclick etc.) to dispatch function calls.
5341
5650
  * @private
5342
5651
  */
5343
- ComponentHandle.prototype._wireActions = function(taco) {
5344
- if (!taco || typeof taco !== 'object' || !taco.t) return;
5652
+ _chp._wireActions = function(taco) {
5653
+ if (!_is(taco, 'object') || !taco.t) return;
5345
5654
  if (taco.a) {
5346
5655
  for (var key in taco.a) {
5347
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
5348
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
5656
+ if (!_hop.call(taco.a, key)) continue;
5657
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
5349
5658
  var actionName = taco.a[key];
5350
5659
  if (actionName in this._actions) {
5351
5660
  var registeredName = this._bwId + '_' + actionName;
@@ -5359,11 +5668,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
5359
5668
  }
5360
5669
  }
5361
5670
  }
5362
- if (Array.isArray(taco.c)) {
5671
+ if (_isA(taco.c)) {
5363
5672
  for (var i = 0; i < taco.c.length; i++) {
5364
5673
  this._wireActions(taco.c[i]);
5365
5674
  }
5366
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
5675
+ } else if (_is(taco.c, 'object') && taco.c.t) {
5367
5676
  this._wireActions(taco.c);
5368
5677
  }
5369
5678
  };
@@ -5372,7 +5681,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
5372
5681
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
5373
5682
  * @private
5374
5683
  */
5375
- ComponentHandle.prototype._deepCloneTaco = function(taco) {
5684
+ _chp._deepCloneTaco = function(taco) {
5376
5685
  if (taco == null) return taco;
5377
5686
  // Preserve _bwWhen / _bwEach markers (contain functions)
5378
5687
  if (taco._bwWhen) {
@@ -5384,18 +5693,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
5384
5693
  if (taco._bwEach) {
5385
5694
  return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
5386
5695
  }
5387
- if (typeof taco !== 'object' || !taco.t) return taco;
5696
+ if (!_is(taco, 'object') || !taco.t) return taco;
5388
5697
  var result = { t: taco.t };
5389
5698
  if (taco.a) {
5390
5699
  result.a = {};
5391
5700
  for (var k in taco.a) {
5392
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
5701
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
5393
5702
  }
5394
5703
  }
5395
5704
  if (taco.c != null) {
5396
- if (Array.isArray(taco.c)) {
5705
+ if (_isA(taco.c)) {
5397
5706
  result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
5398
- } else if (typeof taco.c === 'object') {
5707
+ } else if (_is(taco.c, 'object')) {
5399
5708
  result.c = this._deepCloneTaco(taco.c);
5400
5709
  } else {
5401
5710
  result.c = taco.c;
@@ -5409,27 +5718,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
5409
5718
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
5410
5719
  * @private
5411
5720
  */
5412
- ComponentHandle.prototype._tacoForDOM = function(taco) {
5413
- if (!taco || typeof taco !== 'object' || !taco.t) return taco;
5721
+ _chp._tacoForDOM = function(taco) {
5722
+ if (!_is(taco, 'object') || !taco.t) return taco;
5414
5723
  var result = { t: taco.t };
5415
5724
  if (taco.a) result.a = taco.a;
5416
5725
  if (taco.c != null) {
5417
- if (Array.isArray(taco.c)) {
5726
+ if (_isA(taco.c)) {
5418
5727
  result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
5419
- } else if (typeof taco.c === 'object' && taco.c.t) {
5728
+ } else if (_is(taco.c, 'object') && taco.c.t) {
5420
5729
  result.c = this._tacoForDOM(taco.c);
5421
5730
  } else {
5422
5731
  result.c = taco.c;
5423
5732
  }
5424
5733
  }
5425
5734
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
5735
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
5736
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
5737
+ '>. Use onclick attribute or bw.component() for child interactivity.');
5738
+ }
5426
5739
  return result;
5427
5740
  };
5428
5741
 
5429
5742
  /**
5430
5743
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
5431
5744
  */
5432
- ComponentHandle.prototype.unmount = function() {
5745
+ _chp.unmount = function() {
5433
5746
  if (!this.mounted) return;
5434
5747
 
5435
5748
  // unmount hook
@@ -5464,12 +5777,23 @@ ComponentHandle.prototype.unmount = function() {
5464
5777
  /**
5465
5778
  * Destroy: unmount + clear state + unregister actions.
5466
5779
  */
5467
- ComponentHandle.prototype.destroy = function() {
5780
+ _chp.destroy = function() {
5468
5781
  // willDestroy hook
5469
5782
  if (this._hooks.willDestroy) {
5470
5783
  this._hooks.willDestroy(this);
5471
5784
  }
5472
5785
 
5786
+ // Cascade destroy to children depth-first (Bug #5)
5787
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
5788
+ this._children[ci].destroy();
5789
+ }
5790
+ this._children = [];
5791
+ if (this._parent) {
5792
+ var idx = this._parent._children.indexOf(this);
5793
+ if (idx >= 0) this._parent._children.splice(idx, 1);
5794
+ this._parent = null;
5795
+ }
5796
+
5473
5797
  this.unmount();
5474
5798
 
5475
5799
  // Unregister actions from function registry
@@ -5496,12 +5820,36 @@ ComponentHandle.prototype.destroy = function() {
5496
5820
  * Flush dirty state: resolve changed bindings and apply to DOM.
5497
5821
  * @private
5498
5822
  */
5499
- ComponentHandle.prototype._flush = function() {
5823
+ _chp._flush = function() {
5500
5824
  this._scheduled = false;
5501
- var changedKeys = Object.keys(this._dirtyKeys);
5825
+ var changedKeys = _keys(this._dirtyKeys);
5502
5826
  this._dirtyKeys = {};
5503
5827
  if (changedKeys.length === 0 || !this.mounted) return;
5504
5828
 
5829
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
5830
+ // rebuild the TACO from the factory with merged state (Bug #6)
5831
+ if (this._factory) {
5832
+ var rebuildNeeded = false;
5833
+ for (var fi = 0; fi < changedKeys.length; fi++) {
5834
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
5835
+ rebuildNeeded = true; break;
5836
+ }
5837
+ }
5838
+ if (rebuildNeeded) {
5839
+ var merged = {};
5840
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
5841
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
5842
+ this._factory.props = merged;
5843
+ var newTaco = bw.make(this._factory.type, merged);
5844
+ newTaco._bwFactory = this._factory;
5845
+ this.taco = newTaco;
5846
+ this._originalTaco = this._deepCloneTaco(newTaco);
5847
+ this._render();
5848
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
5849
+ return;
5850
+ }
5851
+ }
5852
+
5505
5853
  // willUpdate hook
5506
5854
  if (this._hooks.willUpdate) {
5507
5855
  this._hooks.willUpdate(this, changedKeys);
@@ -5540,7 +5888,7 @@ ComponentHandle.prototype._flush = function() {
5540
5888
  * Returns list of patches to apply.
5541
5889
  * @private
5542
5890
  */
5543
- ComponentHandle.prototype._resolveBindings = function(changedKeys) {
5891
+ _chp._resolveBindings = function(changedKeys) {
5544
5892
  var patches = [];
5545
5893
  for (var i = 0; i < this._bindings.length; i++) {
5546
5894
  var b = this._bindings[i];
@@ -5576,11 +5924,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
5576
5924
  * Apply patches to DOM.
5577
5925
  * @private
5578
5926
  */
5579
- ComponentHandle.prototype._applyPatches = function(patches) {
5927
+ _chp._applyPatches = function(patches) {
5580
5928
  for (var i = 0; i < patches.length; i++) {
5581
5929
  var p = patches[i];
5582
5930
  var el = this._bw_refs[p.refId];
5583
- if (!el) continue;
5931
+ if (!el) {
5932
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
5933
+ continue;
5934
+ }
5584
5935
  if (p.type === 'content') {
5585
5936
  el.textContent = p.value;
5586
5937
  } else if (p.type === 'attribute') {
@@ -5597,7 +5948,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
5597
5948
  * Resolve all bindings and apply (used for initial render).
5598
5949
  * @private
5599
5950
  */
5600
- ComponentHandle.prototype._resolveAndApplyAll = function() {
5951
+ _chp._resolveAndApplyAll = function() {
5601
5952
  var patches = [];
5602
5953
  for (var i = 0; i < this._bindings.length; i++) {
5603
5954
  var b = this._bindings[i];
@@ -5620,7 +5971,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
5620
5971
  * Full re-render for structural changes (when/each branch switches).
5621
5972
  * @private
5622
5973
  */
5623
- ComponentHandle.prototype._render = function() {
5974
+ _chp._render = function() {
5624
5975
  if (!this.element || !this.element.parentNode) return;
5625
5976
  var parent = this.element.parentNode;
5626
5977
  var nextSibling = this.element.nextSibling;
@@ -5660,7 +6011,7 @@ ComponentHandle.prototype._render = function() {
5660
6011
  * @param {string} event - Event name (e.g., 'click')
5661
6012
  * @param {Function} handler - Event handler
5662
6013
  */
5663
- ComponentHandle.prototype.on = function(event, handler) {
6014
+ _chp.on = function(event, handler) {
5664
6015
  if (this.element) {
5665
6016
  this.element.addEventListener(event, handler);
5666
6017
  }
@@ -5672,7 +6023,7 @@ ComponentHandle.prototype.on = function(event, handler) {
5672
6023
  * @param {string} event - Event name
5673
6024
  * @param {Function} handler - Handler to remove
5674
6025
  */
5675
- ComponentHandle.prototype.off = function(event, handler) {
6026
+ _chp.off = function(event, handler) {
5676
6027
  if (this.element) {
5677
6028
  this.element.removeEventListener(event, handler);
5678
6029
  }
@@ -5687,7 +6038,7 @@ ComponentHandle.prototype.off = function(event, handler) {
5687
6038
  * @param {Function} handler - Handler function
5688
6039
  * @returns {Function} Unsubscribe function
5689
6040
  */
5690
- ComponentHandle.prototype.sub = function(topic, handler) {
6041
+ _chp.sub = function(topic, handler) {
5691
6042
  var unsub = bw.sub(topic, handler);
5692
6043
  this._subs.push(unsub);
5693
6044
  return unsub;
@@ -5698,10 +6049,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
5698
6049
  * @param {string} name - Action name
5699
6050
  * @param {...*} args - Arguments passed after comp
5700
6051
  */
5701
- ComponentHandle.prototype.action = function(name) {
6052
+ _chp.action = function(name) {
5702
6053
  var fn = this._actions[name];
5703
6054
  if (!fn) {
5704
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
6055
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
5705
6056
  return;
5706
6057
  }
5707
6058
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -5713,7 +6064,7 @@ ComponentHandle.prototype.action = function(name) {
5713
6064
  * @param {string} sel - CSS selector
5714
6065
  * @returns {Element|null}
5715
6066
  */
5716
- ComponentHandle.prototype.select = function(sel) {
6067
+ _chp.select = function(sel) {
5717
6068
  return this.element ? this.element.querySelector(sel) : null;
5718
6069
  };
5719
6070
 
@@ -5722,7 +6073,7 @@ ComponentHandle.prototype.select = function(sel) {
5722
6073
  * @param {string} sel - CSS selector
5723
6074
  * @returns {Element[]}
5724
6075
  */
5725
- ComponentHandle.prototype.selectAll = function(sel) {
6076
+ _chp.selectAll = function(sel) {
5726
6077
  if (!this.element) return [];
5727
6078
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
5728
6079
  };
@@ -5733,7 +6084,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
5733
6084
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
5734
6085
  * @returns {ComponentHandle} this (for chaining)
5735
6086
  */
5736
- ComponentHandle.prototype.userTag = function(tag) {
6087
+ _chp.userTag = function(tag) {
5737
6088
  this._userTag = tag;
5738
6089
  if (this.element) {
5739
6090
  this.element.classList.add(tag);
@@ -5834,8 +6185,8 @@ bw.message = function(target, action, data) {
5834
6185
  }
5835
6186
  if (!el || !el._bwComponentHandle) return false;
5836
6187
  var comp = el._bwComponentHandle;
5837
- if (typeof comp[action] !== 'function') {
5838
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
6188
+ if (!_is(comp[action], 'function')) {
6189
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
5839
6190
  return false;
5840
6191
  }
5841
6192
  comp[action](data);
@@ -5872,7 +6223,7 @@ bw._builtinClientFunctions = {
5872
6223
  },
5873
6224
  focus: function(selector) {
5874
6225
  var el = bw._el(selector);
5875
- if (el && typeof el.focus === 'function') el.focus();
6226
+ if (el && _is(el.focus, 'function')) el.focus();
5876
6227
  },
5877
6228
  download: function(filename, content, mimeType) {
5878
6229
  if (typeof document === 'undefined') return;
@@ -6037,12 +6388,12 @@ bw.clientApply = function(msg) {
6037
6388
  } else if (type === 'remove') {
6038
6389
  var toRemove = bw._el(target);
6039
6390
  if (!toRemove) return false;
6040
- if (typeof bw.cleanup === 'function') bw.cleanup(toRemove);
6391
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
6041
6392
  toRemove.remove();
6042
6393
  return true;
6043
6394
 
6044
6395
  } else if (type === 'batch') {
6045
- if (!Array.isArray(msg.ops)) return false;
6396
+ if (!_isA(msg.ops)) return false;
6046
6397
  var allOk = true;
6047
6398
  msg.ops.forEach(function(op) {
6048
6399
  if (!bw.clientApply(op)) allOk = false;
@@ -6058,26 +6409,26 @@ bw.clientApply = function(msg) {
6058
6409
  bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
6059
6410
  return true;
6060
6411
  } catch (e) {
6061
- console.error('[bw] register error:', msg.name, e);
6412
+ _ce('[bw] register error:', msg.name, e);
6062
6413
  return false;
6063
6414
  }
6064
6415
 
6065
6416
  } else if (type === 'call') {
6066
6417
  if (!msg.name) return false;
6067
6418
  var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
6068
- if (typeof fn !== 'function') return false;
6419
+ if (!_is(fn, 'function')) return false;
6069
6420
  try {
6070
- var args = Array.isArray(msg.args) ? msg.args : [];
6421
+ var args = _isA(msg.args) ? msg.args : [];
6071
6422
  fn.apply(null, args);
6072
6423
  return true;
6073
6424
  } catch (e) {
6074
- console.error('[bw] call error:', msg.name, e);
6425
+ _ce('[bw] call error:', msg.name, e);
6075
6426
  return false;
6076
6427
  }
6077
6428
 
6078
6429
  } else if (type === 'exec') {
6079
6430
  if (!bw._allowExec) {
6080
- console.warn('[bw] exec rejected: allowExec is not enabled');
6431
+ _cw('[bw] exec rejected: allowExec is not enabled');
6081
6432
  return false;
6082
6433
  }
6083
6434
  if (!msg.code) return false;
@@ -6085,7 +6436,7 @@ bw.clientApply = function(msg) {
6085
6436
  new Function(msg.code)();
6086
6437
  return true;
6087
6438
  } catch (e) {
6088
- console.error('[bw] exec error:', e);
6439
+ _ce('[bw] exec error:', e);
6089
6440
  return false;
6090
6441
  }
6091
6442
  }
@@ -6133,7 +6484,7 @@ bw.clientConnect = function(url, opts) {
6133
6484
 
6134
6485
  function handleMessage(data) {
6135
6486
  try {
6136
- var msg = typeof data === 'string' ? bw.clientParse(data) : data;
6487
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
6137
6488
  if (onMessage) onMessage(msg);
6138
6489
  if (handlers.message) handlers.message(msg);
6139
6490
  bw.clientApply(msg);
@@ -6171,7 +6522,7 @@ bw.clientConnect = function(url, opts) {
6171
6522
  setStatus('connected');
6172
6523
  conn._pollTimer = setInterval(function() {
6173
6524
  fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
6174
- if (Array.isArray(msgs)) {
6525
+ if (_isA(msgs)) {
6175
6526
  msgs.forEach(handleMessage);
6176
6527
  } else if (msgs && msgs.type) {
6177
6528
  handleMessage(msgs);
@@ -6253,33 +6604,33 @@ bw.inspect = function(target) {
6253
6604
  el = target.element;
6254
6605
  comp = target;
6255
6606
  } else {
6256
- if (typeof target === 'string') {
6607
+ if (_is(target, 'string')) {
6257
6608
  el = bw.$(target)[0];
6258
6609
  }
6259
6610
  if (!el) {
6260
- console.warn('bw.inspect: element not found');
6611
+ _cw('bw.inspect: element not found');
6261
6612
  return null;
6262
6613
  }
6263
6614
  comp = el._bwComponentHandle;
6264
6615
  }
6265
6616
  if (!comp) {
6266
- console.log('bw.inspect: no ComponentHandle on this element');
6267
- console.log(' Tag:', el.tagName);
6268
- console.log(' Classes:', el.className);
6269
- console.log(' _bw_state:', el._bw_state || '(none)');
6617
+ _cl('bw.inspect: no ComponentHandle on this element');
6618
+ _cl(' Tag:', el.tagName);
6619
+ _cl(' Classes:', el.className);
6620
+ _cl(' _bw_state:', el._bw_state || '(none)');
6270
6621
  return null;
6271
6622
  }
6272
6623
  var deps = comp._bindings.reduce(function(s, b) {
6273
6624
  return s.concat(b.deps || []);
6274
6625
  }, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
6275
6626
  console.group('Component: ' + comp._bwId);
6276
- console.log('State:', comp._state);
6277
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
6278
- console.log('Methods:', Object.keys(comp._methods));
6279
- console.log('Actions:', Object.keys(comp._actions));
6280
- console.log('User tag:', comp._userTag || '(none)');
6281
- console.log('Mounted:', comp.mounted);
6282
- console.log('Element:', comp.element);
6627
+ _cl('State:', comp._state);
6628
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
6629
+ _cl('Methods:', _keys(comp._methods));
6630
+ _cl('Actions:', _keys(comp._actions));
6631
+ _cl('User tag:', comp._userTag || '(none)');
6632
+ _cl('Mounted:', comp.mounted);
6633
+ _cl('Element:', comp.element);
6283
6634
  console.groupEnd();
6284
6635
  return comp;
6285
6636
  };
@@ -6302,8 +6653,8 @@ bw.compile = function(taco) {
6302
6653
  // Pre-extract all binding expressions
6303
6654
  var precompiled = [];
6304
6655
  function walkExpressions(node) {
6305
- if (!node || typeof node !== 'object') return;
6306
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
6656
+ if (!_is(node, 'object')) return;
6657
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
6307
6658
  var parsed = bw._parseBindings(node.c);
6308
6659
  for (var i = 0; i < parsed.length; i++) {
6309
6660
  try {
@@ -6318,9 +6669,9 @@ bw.compile = function(taco) {
6318
6669
  }
6319
6670
  if (node.a) {
6320
6671
  for (var key in node.a) {
6321
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
6672
+ if (_hop.call(node.a, key)) {
6322
6673
  var v = node.a[key];
6323
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
6674
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
6324
6675
  var parsed2 = bw._parseBindings(v);
6325
6676
  for (var j = 0; j < parsed2.length; j++) {
6326
6677
  try {
@@ -6336,9 +6687,9 @@ bw.compile = function(taco) {
6336
6687
  }
6337
6688
  }
6338
6689
  }
6339
- if (Array.isArray(node.c)) {
6690
+ if (_isA(node.c)) {
6340
6691
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
6341
- } else if (node.c && typeof node.c === 'object' && node.c.t) {
6692
+ } else if (_is(node.c, 'object') && node.c.t) {
6342
6693
  walkExpressions(node.c);
6343
6694
  }
6344
6695
  }
@@ -6350,7 +6701,7 @@ bw.compile = function(taco) {
6350
6701
  handle._precompiledBindings = precompiled;
6351
6702
  if (initialState) {
6352
6703
  for (var k in initialState) {
6353
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
6704
+ if (_hop.call(initialState, k)) {
6354
6705
  handle._state[k] = initialState[k];
6355
6706
  }
6356
6707
  }
@@ -6381,18 +6732,18 @@ bw.compile = function(taco) {
6381
6732
  bw.css = function(rules, options = {}) {
6382
6733
  const { minify = false, pretty = !minify } = options;
6383
6734
 
6384
- if (typeof rules === 'string') return rules;
6735
+ if (_is(rules, 'string')) return rules;
6385
6736
 
6386
6737
  let css = '';
6387
6738
  const indent = pretty ? ' ' : '';
6388
6739
  const newline = pretty ? '\n' : '';
6389
6740
  const space = pretty ? ' ' : '';
6390
6741
 
6391
- if (Array.isArray(rules)) {
6742
+ if (_isA(rules)) {
6392
6743
  css = rules.map(rule => bw.css(rule, options)).join(newline);
6393
- } else if (typeof rules === 'object') {
6744
+ } else if (_is(rules, 'object')) {
6394
6745
  Object.entries(rules).forEach(([selector, styles]) => {
6395
- if (typeof styles === 'object' && !Array.isArray(styles)) {
6746
+ if (_is(styles, 'object')) {
6396
6747
  // Handle @media, @keyframes, @supports — recurse into nested block
6397
6748
  if (selector.charAt(0) === '@') {
6398
6749
  const inner = bw.css(styles, options);
@@ -6441,7 +6792,7 @@ bw.css = function(rules, options = {}) {
6441
6792
  */
6442
6793
  bw.injectCSS = function(css, options = {}) {
6443
6794
  if (!bw._isBrowser) {
6444
- console.warn('bw.injectCSS requires a DOM environment');
6795
+ _cw('bw.injectCSS requires a DOM environment');
6445
6796
  return null;
6446
6797
  }
6447
6798
 
@@ -6458,7 +6809,7 @@ bw.injectCSS = function(css, options = {}) {
6458
6809
  }
6459
6810
 
6460
6811
  // Convert CSS if needed
6461
- const cssStr = typeof css === 'string' ? css : bw.css(css, options);
6812
+ const cssStr = _is(css, 'string') ? css : bw.css(css, options);
6462
6813
 
6463
6814
  // Set or append CSS
6464
6815
  if (append && styleEl.textContent) {
@@ -6488,7 +6839,7 @@ bw.s = function() {
6488
6839
  var result = {};
6489
6840
  for (var i = 0; i < arguments.length; i++) {
6490
6841
  var arg = arguments[i];
6491
- if (arg && typeof arg === 'object') Object.assign(result, arg);
6842
+ if (_is(arg, 'object')) Object.assign(result, arg);
6492
6843
  }
6493
6844
  return result;
6494
6845
  };
@@ -6611,7 +6962,7 @@ bw.u = {
6611
6962
  bw.responsive = function(selector, breakpoints) {
6612
6963
  var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
6613
6964
  var parts = [];
6614
- Object.keys(breakpoints).forEach(function(key) {
6965
+ _keys(breakpoints).forEach(function(key) {
6615
6966
  var rules = {};
6616
6967
  if (key === 'base') {
6617
6968
  rules[selector] = breakpoints[key];
@@ -6683,18 +7034,18 @@ if (bw._isBrowser) {
6683
7034
  if (!selector) return [];
6684
7035
 
6685
7036
  // Already an array
6686
- if (Array.isArray(selector)) return selector;
7037
+ if (_isA(selector)) return selector;
6687
7038
 
6688
7039
  // Single element
6689
7040
  if (selector.nodeType) return [selector];
6690
7041
 
6691
7042
  // NodeList or HTMLCollection
6692
- if (selector.length !== undefined && typeof selector !== 'string') {
7043
+ if (selector.length !== undefined && !_is(selector, 'string')) {
6693
7044
  return Array.from(selector);
6694
7045
  }
6695
7046
 
6696
7047
  // CSS selector string
6697
- if (typeof selector === 'string') {
7048
+ if (_is(selector, 'string')) {
6698
7049
  return Array.from(document.querySelectorAll(selector));
6699
7050
  }
6700
7051
 
@@ -7198,7 +7549,7 @@ bw.makeTable = function(config) {
7198
7549
 
7199
7550
  // Auto-detect columns if not provided
7200
7551
  const cols = columns || (data.length > 0
7201
- ? Object.keys(data[0]).map(key => ({ key, label: key }))
7552
+ ? _keys(data[0]).map(key => ({ key, label: key }))
7202
7553
  : []);
7203
7554
 
7204
7555
  // Current sort state
@@ -7213,7 +7564,7 @@ bw.makeTable = function(config) {
7213
7564
  const bVal = b[currentSortColumn];
7214
7565
 
7215
7566
  // Handle different types
7216
- if (typeof aVal === 'number' && typeof bVal === 'number') {
7567
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
7217
7568
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
7218
7569
  }
7219
7570
 
@@ -7323,7 +7674,7 @@ bw.makeTable = function(config) {
7323
7674
  bw.makeTableFromArray = function(config) {
7324
7675
  const { data = [], headerRow = true, columns, ...rest } = config;
7325
7676
 
7326
- if (!Array.isArray(data) || data.length === 0) {
7677
+ if (!_isA(data) || data.length === 0) {
7327
7678
  return bw.makeTable({ data: [], columns: columns || [], ...rest });
7328
7679
  }
7329
7680
 
@@ -7405,7 +7756,7 @@ bw.makeBarChart = function(config) {
7405
7756
  className = ''
7406
7757
  } = config;
7407
7758
 
7408
- if (!Array.isArray(data) || data.length === 0) {
7759
+ if (!_isA(data) || data.length === 0) {
7409
7760
  return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
7410
7761
  }
7411
7762
 
@@ -7554,7 +7905,7 @@ bw._componentRegistry = new Map();
7554
7905
  */
7555
7906
  bw.render = function(element, position, taco) {
7556
7907
  // Get target element
7557
- const targetEl = typeof element === 'string'
7908
+ const targetEl = _is(element, 'string')
7558
7909
  ? document.querySelector(element)
7559
7910
  : element;
7560
7911
 
@@ -7704,7 +8055,7 @@ bw.render = function(element, position, taco) {
7704
8055
  setContent(content) {
7705
8056
  this._taco.c = content;
7706
8057
  if (this.element) {
7707
- if (typeof content === 'string') {
8058
+ if (_is(content, 'string')) {
7708
8059
  this.element.textContent = content;
7709
8060
  } else {
7710
8061
  // Re-render for complex content