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