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 v2.0.16 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  /**
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
  /**
@@ -6874,7 +6874,11 @@ var BCCL = {
6874
6874
  function make(type, props) {
6875
6875
  var def = BCCL[type];
6876
6876
  if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
6877
- return def.make(props || {});
6877
+ var taco = def.make(props || {});
6878
+ if (taco && typeof taco === 'object') {
6879
+ taco._bwFactory = { type: type, props: props || {} };
6880
+ }
6881
+ return taco;
6878
6882
  }
6879
6883
 
6880
6884
  var components = /*#__PURE__*/Object.freeze({
@@ -6995,7 +6999,7 @@ const bw = {
6995
6999
  __monkey_patch_is_nodejs__: {
6996
7000
  _value: 'ignore',
6997
7001
  set: function(x) {
6998
- this._value = (typeof x === 'boolean') ? x : 'ignore';
7002
+ this._value = _is(x, 'boolean') ? x : 'ignore';
6999
7003
  },
7000
7004
  get: function() {
7001
7005
  return this._value;
@@ -7043,6 +7047,67 @@ Object.defineProperty(bw, '_isBrowser', {
7043
7047
  configurable: true
7044
7048
  });
7045
7049
 
7050
+ // ── Internal aliases ─────────────────────────────────────────────────────
7051
+ // Short names for frequently-used builtins and internal methods.
7052
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
7053
+ //
7054
+ // Why: Terser can't shorten global property chains (console.warn,
7055
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
7056
+ // because it can't prove they're side-effect-free. We can, so we alias
7057
+ // them here. Each alias saves bytes in the minified output, and the short
7058
+ // names also reduce visual noise in the hot paths (binding pipeline,
7059
+ // createDOM, etc.).
7060
+ //
7061
+ // Alias Target Sites
7062
+ // ───────── ────────────────────────────────────── ─────
7063
+ // _hop Object.prototype.hasOwnProperty 15
7064
+ // _isA Array.isArray 25
7065
+ // _keys Object.keys 7
7066
+ // _to bw.typeOf (type string) 26
7067
+ // _is type check boolean: _is(x,'string') ~50
7068
+ // _cw console.warn 8
7069
+ // _cl console.log 11
7070
+ // _ce console.error 4
7071
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
7072
+ //
7073
+ // Note: document.createElement etc. are NOT aliased because they require
7074
+ // `this === document` and .bind() would add overhead on every call.
7075
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
7076
+ // patching of console.warn/log/error continues to work.
7077
+ //
7078
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
7079
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
7080
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
7081
+ //
7082
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
7083
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
7084
+ // returns trueVal if _to(x)===type, else falseVal.
7085
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
7086
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
7087
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
7088
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
7089
+ // Uncomment if pattern frequency justifies them:
7090
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
7091
+ // 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); };
7092
+ // ─────────────────────────────────────────────────────────────────────────
7093
+ var _hop = Object.prototype.hasOwnProperty;
7094
+ var _isA = Array.isArray;
7095
+ var _keys = Object.keys;
7096
+ var _to = typeOf; // imported from bitwrench-utils.js
7097
+ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
7098
+ // Console aliases use thin wrappers (not direct references) so that test
7099
+ // code can monkey-patch console.warn/log/error and the patches take effect.
7100
+ var _cw = function() { console.warn.apply(console, arguments); };
7101
+ var _cl = function() { console.log.apply(console, arguments); };
7102
+ var _ce = function() { console.error.apply(console, arguments); };
7103
+
7104
+ /**
7105
+ * Debug flag. When true, emits console.warn for silent binding failures
7106
+ * (missing paths, null refs, auto-created intermediate objects).
7107
+ * @type {boolean}
7108
+ */
7109
+ bw.debug = false;
7110
+
7046
7111
  /**
7047
7112
  * Lazy-resolve Node.js `fs` module.
7048
7113
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -7190,7 +7255,7 @@ bw.uuid = function(prefix) {
7190
7255
  */
7191
7256
  bw._el = function(id) {
7192
7257
  // Pass-through for DOM elements
7193
- if (typeof id !== 'string') return id || null;
7258
+ if (!_is(id, 'string')) return id || null;
7194
7259
  if (!id) return null;
7195
7260
  if (!bw._isBrowser) return null;
7196
7261
 
@@ -7286,7 +7351,7 @@ bw._deregisterNode = function(el, bwId) {
7286
7351
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
7287
7352
  */
7288
7353
  bw.escapeHTML = function(str) {
7289
- if (typeof str !== 'string') return '';
7354
+ if (!_is(str, 'string')) return '';
7290
7355
 
7291
7356
  const escapeMap = {
7292
7357
  '&': '&amp;',
@@ -7359,7 +7424,7 @@ bw.html = function(taco, options = {}) {
7359
7424
  }
7360
7425
 
7361
7426
  // Handle arrays of TACOs
7362
- if (Array.isArray(taco)) {
7427
+ if (_isA(taco)) {
7363
7428
  return taco.map(t => bw.html(t, options)).join('');
7364
7429
  }
7365
7430
 
@@ -7382,15 +7447,15 @@ bw.html = function(taco, options = {}) {
7382
7447
  if (taco && taco._bwEach && options.state) {
7383
7448
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
7384
7449
  var arr = bw._evaluatePath(options.state, eachExpr);
7385
- if (!Array.isArray(arr)) return '';
7450
+ if (!_isA(arr)) return '';
7386
7451
  return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
7387
7452
  }
7388
7453
 
7389
7454
  // Handle primitives and non-TACO objects
7390
- if (typeof taco !== 'object' || !taco.t) {
7455
+ if (!_is(taco, 'object') || !taco.t) {
7391
7456
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
7392
7457
  // Resolve template bindings if state provided
7393
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
7458
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
7394
7459
  str = bw._resolveTemplate(str, options.state, !!options.compile);
7395
7460
  }
7396
7461
  return str;
@@ -7410,10 +7475,18 @@ bw.html = function(taco, options = {}) {
7410
7475
  // Skip null, undefined, false
7411
7476
  if (value == null || value === false) continue;
7412
7477
 
7413
- // Skip event handlers (they're for DOM only)
7414
- if (key.startsWith('on')) continue;
7478
+ // Serialize event handlers via funcRegister
7479
+ if (key.startsWith('on')) {
7480
+ if (_is(value, 'function')) {
7481
+ var fnId = bw.funcRegister(value);
7482
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
7483
+ } else if (_is(value, 'string')) {
7484
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
7485
+ }
7486
+ continue;
7487
+ }
7415
7488
 
7416
- if (key === 'style' && typeof value === 'object') {
7489
+ if (key === 'style' && _is(value, 'object')) {
7417
7490
  // Convert style object to string
7418
7491
  const styleStr = Object.entries(value)
7419
7492
  .filter(([, v]) => v != null)
@@ -7424,7 +7497,7 @@ bw.html = function(taco, options = {}) {
7424
7497
  }
7425
7498
  } else if (key === 'class') {
7426
7499
  // Handle class as array or string
7427
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
7500
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
7428
7501
  if (classStr) {
7429
7502
  attrStr += ` class="${bw.escapeHTML(classStr)}"`;
7430
7503
  }
@@ -7460,13 +7533,184 @@ bw.html = function(taco, options = {}) {
7460
7533
  // Process content recursively
7461
7534
  let contentStr = content != null ? bw.html(content, options) : '';
7462
7535
  // Resolve template bindings in content if state provided
7463
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
7536
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
7464
7537
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
7465
7538
  }
7466
7539
 
7467
7540
  return `<${tag}${attrStr}>${contentStr}</${tag}>`;
7468
7541
  };
7469
7542
 
7543
+ /**
7544
+ * Generate a complete, self-contained HTML document from TACO content.
7545
+ *
7546
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
7547
+ * func registry emission (so serialized event handlers work), optional theme,
7548
+ * and extra head elements. Designed for static site generation, offline/airgapped
7549
+ * use, and the "static site that isn't static" workflow.
7550
+ *
7551
+ * @param {Object} [opts={}] - Page options
7552
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
7553
+ * @param {string} [opts.title='bitwrench'] - Page title
7554
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
7555
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
7556
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
7557
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
7558
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
7559
+ * @param {string} [opts.favicon=''] - Favicon URL
7560
+ * @param {string} [opts.lang='en'] - HTML lang attribute
7561
+ * @returns {string} Complete HTML document string
7562
+ * @category DOM Generation
7563
+ * @see bw.html
7564
+ * @example
7565
+ * bw.htmlPage({
7566
+ * title: 'My App',
7567
+ * body: { t: 'h1', c: 'Hello World' },
7568
+ * runtime: 'shim'
7569
+ * })
7570
+ */
7571
+ bw.htmlPage = function(opts) {
7572
+ opts = opts || {};
7573
+ var title = opts.title || 'bitwrench';
7574
+ var body = opts.body || '';
7575
+ var state = opts.state || undefined;
7576
+ var runtime = opts.runtime || 'shim';
7577
+ var css = opts.css || '';
7578
+ var theme = opts.theme || null;
7579
+ var headExtra = opts.head || [];
7580
+ var favicon = opts.favicon || '';
7581
+ var lang = opts.lang || 'en';
7582
+
7583
+ // Snapshot funcRegistry counter before rendering
7584
+ var fnCounterBefore = bw._fnIDCounter;
7585
+
7586
+ // Render body content
7587
+ var bodyHTML = '';
7588
+ if (_is(body, 'string')) {
7589
+ bodyHTML = body;
7590
+ } else {
7591
+ var htmlOpts = {};
7592
+ if (state) htmlOpts.state = state;
7593
+ bodyHTML = bw.html(body, htmlOpts);
7594
+ }
7595
+
7596
+ // Collect functions registered during this render
7597
+ var fnCounterAfter = bw._fnIDCounter;
7598
+ var registryEntries = '';
7599
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
7600
+ var fnKey = 'bw_fn_' + i;
7601
+ if (bw._fnRegistry[fnKey]) {
7602
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
7603
+ bw._fnRegistry[fnKey].toString() + ';\n';
7604
+ }
7605
+ }
7606
+
7607
+ // Build runtime script for <head>
7608
+ var runtimeHead = '';
7609
+ if (runtime === 'inline') {
7610
+ // Read UMD bundle synchronously if in Node.js
7611
+ var umdSource = null;
7612
+ if (bw._isNode) {
7613
+ try {
7614
+ var fs = (typeof require === 'function') ? require('fs') : null;
7615
+ var pathMod = (typeof require === 'function') ? require('path') : null;
7616
+ if (fs && pathMod) {
7617
+ // Resolve dist/ relative to this source file
7618
+ var srcDir = '';
7619
+ try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
7620
+ catch(e2) { /* ESM: __filename not available */ }
7621
+ if (!srcDir && typeof import.meta !== 'undefined' && import.meta.url) {
7622
+ var url = (typeof require === 'function') ? require('url') : null;
7623
+ if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath(import.meta.url));
7624
+ }
7625
+ if (srcDir) {
7626
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
7627
+ umdSource = fs.readFileSync(distPath, 'utf8');
7628
+ }
7629
+ }
7630
+ } catch(e) { /* fall through */ }
7631
+ }
7632
+ if (umdSource) {
7633
+ runtimeHead = '<script>' + umdSource + '</script>';
7634
+ } else {
7635
+ // Fallback to shim in browser or if dist not available
7636
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
7637
+ }
7638
+ } else if (runtime === 'cdn') {
7639
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
7640
+ } else if (runtime === 'shim') {
7641
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
7642
+ }
7643
+ // runtime === 'none' → empty
7644
+
7645
+ // Theme CSS
7646
+ var themeCSS = '';
7647
+ if (theme) {
7648
+ var themeConfig = _is(theme, 'string')
7649
+ ? (THEME_PRESETS[theme.toLowerCase()] || null)
7650
+ : theme;
7651
+ if (themeConfig) {
7652
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
7653
+ themeCSS = themeResult.css;
7654
+ }
7655
+ }
7656
+
7657
+ // Extra <head> elements
7658
+ var headHTML = '';
7659
+ if (_isA(headExtra) && headExtra.length > 0) {
7660
+ headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
7661
+ }
7662
+
7663
+ // Favicon
7664
+ var faviconTag = '';
7665
+ if (favicon) {
7666
+ var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
7667
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c];
7668
+ });
7669
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
7670
+ }
7671
+
7672
+ // Escaped title
7673
+ var safeTitle = bw.escapeHTML(title);
7674
+
7675
+ // Combine all CSS
7676
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
7677
+
7678
+ // Body-end script: registry entries + optional loadDefaultStyles
7679
+ var bodyEndScript = '';
7680
+ var bodyEndParts = [];
7681
+ if (registryEntries) {
7682
+ bodyEndParts.push(registryEntries);
7683
+ }
7684
+ if (runtime === 'inline' || runtime === 'cdn') {
7685
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
7686
+ }
7687
+ if (bodyEndParts.length > 0) {
7688
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
7689
+ }
7690
+
7691
+ // Assemble document
7692
+ var parts = [
7693
+ '<!DOCTYPE html>',
7694
+ '<html lang="' + lang + '">',
7695
+ '<head>',
7696
+ '<meta charset="UTF-8">',
7697
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
7698
+ ];
7699
+ parts.push('<title>' + safeTitle + '</title>');
7700
+ if (faviconTag) parts.push(faviconTag);
7701
+ if (runtimeHead) parts.push(runtimeHead);
7702
+ if (headHTML) parts.push(headHTML);
7703
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
7704
+ parts.push('</head>');
7705
+ parts.push('<body>');
7706
+ parts.push(bodyHTML);
7707
+ if (bodyEndScript) parts.push(bodyEndScript);
7708
+ parts.push('</body>');
7709
+ parts.push('</html>');
7710
+
7711
+ return parts.join('\n');
7712
+ };
7713
+
7470
7714
  /**
7471
7715
  * Create a live DOM element from a TACO object (browser only).
7472
7716
  *
@@ -7511,7 +7755,7 @@ bw.createDOM = function(taco, options = {}) {
7511
7755
  }
7512
7756
 
7513
7757
  // Handle text nodes
7514
- if (typeof taco !== 'object' || !taco.t) {
7758
+ if (!_is(taco, 'object') || !taco.t) {
7515
7759
  return document.createTextNode(String(taco));
7516
7760
  }
7517
7761
 
@@ -7524,16 +7768,16 @@ bw.createDOM = function(taco, options = {}) {
7524
7768
  for (const [key, value] of Object.entries(attrs)) {
7525
7769
  if (value == null || value === false) continue;
7526
7770
 
7527
- if (key === 'style' && typeof value === 'object') {
7771
+ if (key === 'style' && _is(value, 'object')) {
7528
7772
  // Apply styles directly
7529
7773
  Object.assign(el.style, value);
7530
7774
  } else if (key === 'class') {
7531
7775
  // Handle class as array or string
7532
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
7776
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
7533
7777
  if (classStr) {
7534
7778
  el.className = classStr;
7535
7779
  }
7536
- } else if (key.startsWith('on') && typeof value === 'function') {
7780
+ } else if (key.startsWith('on') && _is(value, 'function')) {
7537
7781
  // Event handlers
7538
7782
  const eventName = key.slice(2).toLowerCase();
7539
7783
  el.addEventListener(eventName, value);
@@ -7553,7 +7797,7 @@ bw.createDOM = function(taco, options = {}) {
7553
7797
  // Children with data-bw_id or id attributes get local refs on the parent,
7554
7798
  // so o.render functions can access them without any DOM lookup.
7555
7799
  if (content != null) {
7556
- if (Array.isArray(content)) {
7800
+ if (_isA(content)) {
7557
7801
  content.forEach(child => {
7558
7802
  if (child != null) {
7559
7803
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -7573,20 +7817,20 @@ bw.createDOM = function(taco, options = {}) {
7573
7817
  if (childEl._bw_refs) {
7574
7818
  if (!el._bw_refs) el._bw_refs = {};
7575
7819
  for (var rk in childEl._bw_refs) {
7576
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
7820
+ if (_hop.call(childEl._bw_refs, rk)) {
7577
7821
  el._bw_refs[rk] = childEl._bw_refs[rk];
7578
7822
  }
7579
7823
  }
7580
7824
  }
7581
7825
  }
7582
7826
  });
7583
- } else if (typeof content === 'object' && content.__bw_raw) {
7827
+ } else if (_is(content, 'object') && content.__bw_raw) {
7584
7828
  // Raw HTML content — inject via innerHTML
7585
7829
  el.innerHTML = content.v;
7586
7830
  } else if (content._bwComponent === true) {
7587
7831
  // Single ComponentHandle as content
7588
7832
  content.mount(el);
7589
- } else if (typeof content === 'object' && content.t) {
7833
+ } else if (_is(content, 'object') && content.t) {
7590
7834
  var childEl = bw.createDOM(content, options);
7591
7835
  el.appendChild(childEl);
7592
7836
  var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
@@ -7597,7 +7841,7 @@ bw.createDOM = function(taco, options = {}) {
7597
7841
  if (childEl._bw_refs) {
7598
7842
  if (!el._bw_refs) el._bw_refs = {};
7599
7843
  for (var rk in childEl._bw_refs) {
7600
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
7844
+ if (_hop.call(childEl._bw_refs, rk)) {
7601
7845
  el._bw_refs[rk] = childEl._bw_refs[rk];
7602
7846
  }
7603
7847
  }
@@ -7630,7 +7874,7 @@ bw.createDOM = function(taco, options = {}) {
7630
7874
  el._bw_render = opts.render;
7631
7875
 
7632
7876
  if (opts.mounted) {
7633
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
7877
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
7634
7878
  }
7635
7879
 
7636
7880
  // Queue initial render (same timing as mounted)
@@ -7703,7 +7947,7 @@ bw.DOM = function(target, taco, options = {}) {
7703
7947
  const targetEl = bw._el(target);
7704
7948
 
7705
7949
  if (!targetEl) {
7706
- console.error('bw.DOM: Target element not found:', target);
7950
+ _ce('bw.DOM: Target element not found:', target);
7707
7951
  return null;
7708
7952
  }
7709
7953
 
@@ -7743,7 +7987,7 @@ bw.DOM = function(target, taco, options = {}) {
7743
7987
  targetEl.appendChild(taco.element);
7744
7988
  }
7745
7989
  // Handle arrays
7746
- else if (Array.isArray(taco)) {
7990
+ else if (_isA(taco)) {
7747
7991
  taco.forEach(t => {
7748
7992
  if (t != null) {
7749
7993
  if (t._bwComponent === true) {
@@ -7779,7 +8023,7 @@ bw.DOM = function(target, taco, options = {}) {
7779
8023
  bw.compileProps = function(handle, props = {}) {
7780
8024
  const compiledProps = {};
7781
8025
 
7782
- Object.keys(props).forEach(key => {
8026
+ _keys(props).forEach(key => {
7783
8027
  // Create getter/setter for each prop
7784
8028
  Object.defineProperty(compiledProps, key, {
7785
8029
  get() {
@@ -8097,17 +8341,17 @@ bw.patch = function(id, content, attr) {
8097
8341
  if (attr) {
8098
8342
  // Patch an attribute
8099
8343
  el.setAttribute(attr, String(content));
8100
- } else if (Array.isArray(content)) {
8344
+ } else if (_isA(content)) {
8101
8345
  // Patch with array of children (strings and/or TACOs)
8102
8346
  el.innerHTML = '';
8103
8347
  content.forEach(function(item) {
8104
- if (typeof item === 'string' || typeof item === 'number') {
8348
+ if (_is(item, 'string') || _is(item, 'number')) {
8105
8349
  el.appendChild(document.createTextNode(String(item)));
8106
8350
  } else if (item && item.t) {
8107
8351
  el.appendChild(bw.createDOM(item));
8108
8352
  }
8109
8353
  });
8110
- } else if (typeof content === 'object' && content !== null && content.t) {
8354
+ } else if (_is(content, 'object') && content.t) {
8111
8355
  // Patch with a TACO — replace children
8112
8356
  el.innerHTML = '';
8113
8357
  el.appendChild(bw.createDOM(content));
@@ -8138,7 +8382,7 @@ bw.patch = function(id, content, attr) {
8138
8382
  bw.patchAll = function(patches) {
8139
8383
  var results = {};
8140
8384
  for (var id in patches) {
8141
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
8385
+ if (_hop.call(patches, id)) {
8142
8386
  results[id] = bw.patch(id, patches[id]);
8143
8387
  }
8144
8388
  }
@@ -8235,7 +8479,7 @@ bw.pub = function(topic, detail) {
8235
8479
  snapshot[i].handler(detail);
8236
8480
  called++;
8237
8481
  } catch (err) {
8238
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
8482
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
8239
8483
  }
8240
8484
  }
8241
8485
  return called;
@@ -8331,8 +8575,8 @@ bw._fnIDCounter = 0;
8331
8575
  * @see bw.funcGetDispatchStr
8332
8576
  */
8333
8577
  bw.funcRegister = function(fn, name) {
8334
- if (typeof fn !== 'function') return '';
8335
- var fnID = (typeof name === 'string' && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
8578
+ if (!_is(fn, 'function')) return '';
8579
+ var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
8336
8580
  bw._fnRegistry[fnID] = fn;
8337
8581
  return fnID;
8338
8582
  };
@@ -8351,7 +8595,7 @@ bw.funcRegister = function(fn, name) {
8351
8595
  bw.funcGetById = function(name, errFn) {
8352
8596
  name = String(name);
8353
8597
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
8354
- return (typeof errFn === 'function') ? errFn : function() { console.warn('bw.funcGetById: unregistered fn "' + name + '"'); };
8598
+ return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
8355
8599
  };
8356
8600
 
8357
8601
  /**
@@ -8392,13 +8636,30 @@ bw.funcUnregister = function(name) {
8392
8636
  bw.funcGetRegistry = function() {
8393
8637
  var copy = {};
8394
8638
  for (var k in bw._fnRegistry) {
8395
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
8639
+ if (_hop.call(bw._fnRegistry, k)) {
8396
8640
  copy[k] = bw._fnRegistry[k];
8397
8641
  }
8398
8642
  }
8399
8643
  return copy;
8400
8644
  };
8401
8645
 
8646
+ /**
8647
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
8648
+ * When embedded in a `<script>` tag, provides just enough infrastructure
8649
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
8650
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
8651
+ * @type {string}
8652
+ * @category Function Registry
8653
+ */
8654
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
8655
+ 'if(!bw._fnRegistry)bw._fnRegistry={};' +
8656
+ 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
8657
+ 'console.warn("bw: unregistered fn "+n)};};' +
8658
+ 'bw.funcRegister=function(fn,name){' +
8659
+ 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
8660
+ 'bw._fnRegistry[id]=fn;return id;};' +
8661
+ 'window.bw=bw;})();';
8662
+
8402
8663
  // ===================================================================================
8403
8664
  // Template Binding Utilities
8404
8665
  // ===================================================================================
@@ -8426,7 +8687,10 @@ bw._evaluatePath = function(state, path) {
8426
8687
  var parts = path.split('.');
8427
8688
  var val = state;
8428
8689
  for (var i = 0; i < parts.length; i++) {
8429
- if (val == null) return '';
8690
+ if (val == null) {
8691
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
8692
+ return '';
8693
+ }
8430
8694
  val = val[parts[i]];
8431
8695
  }
8432
8696
  return (val == null) ? '' : val;
@@ -8446,7 +8710,7 @@ bw._evaluatePath = function(state, path) {
8446
8710
  */
8447
8711
  bw._compiledExprs = {};
8448
8712
  bw._resolveTemplate = function(str, state, compile) {
8449
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
8713
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
8450
8714
  var bindings = bw._parseBindings(str);
8451
8715
  if (bindings.length === 0) return str;
8452
8716
 
@@ -8468,6 +8732,7 @@ bw._resolveTemplate = function(str, state, compile) {
8468
8732
  try {
8469
8733
  val = bw._compiledExprs[b.expr](state);
8470
8734
  } catch (e) {
8735
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
8471
8736
  val = '';
8472
8737
  }
8473
8738
  } else {
@@ -8576,7 +8841,7 @@ function ComponentHandle(taco) {
8576
8841
  this._state = {};
8577
8842
  if (o.state) {
8578
8843
  for (var k in o.state) {
8579
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
8844
+ if (_hop.call(o.state, k)) {
8580
8845
  this._state[k] = o.state[k];
8581
8846
  }
8582
8847
  }
@@ -8585,7 +8850,7 @@ function ComponentHandle(taco) {
8585
8850
  this._actions = {};
8586
8851
  if (o.actions) {
8587
8852
  for (var k2 in o.actions) {
8588
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
8853
+ if (_hop.call(o.actions, k2)) {
8589
8854
  this._actions[k2] = o.actions[k2];
8590
8855
  }
8591
8856
  }
@@ -8595,7 +8860,7 @@ function ComponentHandle(taco) {
8595
8860
  if (o.methods) {
8596
8861
  var self = this;
8597
8862
  for (var k3 in o.methods) {
8598
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
8863
+ if (_hop.call(o.methods, k3)) {
8599
8864
  this._methods[k3] = o.methods[k3];
8600
8865
  (function(methodName, methodFn) {
8601
8866
  self[methodName] = function() {
@@ -8628,14 +8893,23 @@ function ComponentHandle(taco) {
8628
8893
  this._compile = !!o.compile;
8629
8894
  this._bw_refs = {};
8630
8895
  this._refCounter = 0;
8896
+ // Child component ownership (Bug #5)
8897
+ this._children = [];
8898
+ this._parent = null;
8899
+ // Factory metadata for BCCL rebuild (Bug #6)
8900
+ this._factory = taco._bwFactory || null;
8631
8901
  }
8632
8902
 
8903
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
8904
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
8905
+ var _chp = ComponentHandle.prototype;
8906
+
8633
8907
  // ── State Methods ──
8634
8908
 
8635
8909
  /**
8636
8910
  * Get a state value. Dot-path supported: `get('user.name')`
8637
8911
  */
8638
- ComponentHandle.prototype.get = function(key) {
8912
+ _chp.get = function(key) {
8639
8913
  return bw._evaluatePath(this._state, key);
8640
8914
  };
8641
8915
 
@@ -8645,12 +8919,13 @@ ComponentHandle.prototype.get = function(key) {
8645
8919
  * @param {*} value - New value
8646
8920
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
8647
8921
  */
8648
- ComponentHandle.prototype.set = function(key, value, opts) {
8922
+ _chp.set = function(key, value, opts) {
8649
8923
  // Dot-path set
8650
8924
  var parts = key.split('.');
8651
8925
  var obj = this._state;
8652
8926
  for (var i = 0; i < parts.length - 1; i++) {
8653
- if (obj[parts[i]] == null || typeof obj[parts[i]] !== 'object') {
8927
+ if (!_is(obj[parts[i]], 'object')) {
8928
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
8654
8929
  obj[parts[i]] = {};
8655
8930
  }
8656
8931
  obj = obj[parts[i]];
@@ -8670,10 +8945,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
8670
8945
  /**
8671
8946
  * Get a shallow clone of the full state.
8672
8947
  */
8673
- ComponentHandle.prototype.getState = function() {
8948
+ _chp.getState = function() {
8674
8949
  var clone = {};
8675
8950
  for (var k in this._state) {
8676
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
8951
+ if (_hop.call(this._state, k)) {
8677
8952
  clone[k] = this._state[k];
8678
8953
  }
8679
8954
  }
@@ -8685,9 +8960,9 @@ ComponentHandle.prototype.getState = function() {
8685
8960
  * @param {Object} updates - Key-value pairs to merge
8686
8961
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
8687
8962
  */
8688
- ComponentHandle.prototype.setState = function(updates, opts) {
8963
+ _chp.setState = function(updates, opts) {
8689
8964
  for (var k in updates) {
8690
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
8965
+ if (_hop.call(updates, k)) {
8691
8966
  this._state[k] = updates[k];
8692
8967
  this._dirtyKeys[k] = true;
8693
8968
  }
@@ -8704,9 +8979,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
8704
8979
  /**
8705
8980
  * Push a value onto an array in state. Clones the array.
8706
8981
  */
8707
- ComponentHandle.prototype.push = function(key, val) {
8982
+ _chp.push = function(key, val) {
8708
8983
  var arr = this.get(key);
8709
- var newArr = Array.isArray(arr) ? arr.slice() : [];
8984
+ var newArr = _isA(arr) ? arr.slice() : [];
8710
8985
  newArr.push(val);
8711
8986
  this.set(key, newArr);
8712
8987
  };
@@ -8714,9 +8989,9 @@ ComponentHandle.prototype.push = function(key, val) {
8714
8989
  /**
8715
8990
  * Splice an array in state. Clones the array.
8716
8991
  */
8717
- ComponentHandle.prototype.splice = function(key, start, deleteCount) {
8992
+ _chp.splice = function(key, start, deleteCount) {
8718
8993
  var arr = this.get(key);
8719
- var newArr = Array.isArray(arr) ? arr.slice() : [];
8994
+ var newArr = _isA(arr) ? arr.slice() : [];
8720
8995
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
8721
8996
  Array.prototype.splice.apply(newArr, args);
8722
8997
  this.set(key, newArr);
@@ -8724,7 +8999,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
8724
8999
 
8725
9000
  // ── Scheduling ──
8726
9001
 
8727
- ComponentHandle.prototype._scheduleDirty = function() {
9002
+ _chp._scheduleDirty = function() {
8728
9003
  if (!this._scheduled) {
8729
9004
  this._scheduled = true;
8730
9005
  bw._dirtyComponents.push(this);
@@ -8739,17 +9014,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
8739
9014
  * Creates binding descriptors with refIds for targeted DOM updates.
8740
9015
  * @private
8741
9016
  */
8742
- ComponentHandle.prototype._compileBindings = function() {
9017
+ _chp._compileBindings = function() {
8743
9018
  this._bindings = [];
8744
9019
  this._refCounter = 0;
8745
- var stateKeys = Object.keys(this._state);
9020
+ var stateKeys = _keys(this._state);
8746
9021
  var self = this;
8747
9022
 
8748
9023
  function walkTaco(taco, path) {
8749
- if (taco == null || typeof taco !== 'object' || !taco.t) return taco;
9024
+ if (!_is(taco, 'object') || !taco.t) return taco;
8750
9025
 
8751
9026
  // Check content for bindings
8752
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
9027
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
8753
9028
  var refId = 'bw_ref_' + self._refCounter++;
8754
9029
  var parsed = bw._parseBindings(taco.c);
8755
9030
  var deps = [];
@@ -8771,10 +9046,10 @@ ComponentHandle.prototype._compileBindings = function() {
8771
9046
  // Check attributes for bindings
8772
9047
  if (taco.a) {
8773
9048
  for (var attrName in taco.a) {
8774
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
9049
+ if (!_hop.call(taco.a, attrName)) continue;
8775
9050
  if (attrName === 'data-bw_ref') continue;
8776
9051
  var attrVal = taco.a[attrName];
8777
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
9052
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
8778
9053
  var refId2 = 'bw_ref_' + self._refCounter++;
8779
9054
  var parsed2 = bw._parseBindings(attrVal);
8780
9055
  var deps2 = [];
@@ -8800,9 +9075,27 @@ ComponentHandle.prototype._compileBindings = function() {
8800
9075
  }
8801
9076
 
8802
9077
  // Recurse into children
8803
- if (Array.isArray(taco.c)) {
9078
+ if (_isA(taco.c)) {
8804
9079
  for (var i = 0; i < taco.c.length; i++) {
8805
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
9080
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
9081
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
9082
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
9083
+ var mixedParsed = bw._parseBindings(taco.c[i]);
9084
+ var mixedDeps = [];
9085
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
9086
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
9087
+ }
9088
+ self._bindings.push({
9089
+ expr: taco.c[i],
9090
+ type: 'content',
9091
+ refId: mixedRefId,
9092
+ deps: mixedDeps,
9093
+ template: taco.c[i]
9094
+ });
9095
+ // Replace string with a span wrapper so textContent targets the span only
9096
+ taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
9097
+ }
9098
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
8806
9099
  walkTaco(taco.c[i], path.concat(i));
8807
9100
  }
8808
9101
  // Handle bw.when/bw.each markers
@@ -8837,7 +9130,7 @@ ComponentHandle.prototype._compileBindings = function() {
8837
9130
  taco.c[i]._refId = eachRefId;
8838
9131
  }
8839
9132
  }
8840
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9133
+ } else if (_is(taco.c, 'object') && taco.c.t) {
8841
9134
  walkTaco(taco.c, path.concat(0));
8842
9135
  }
8843
9136
 
@@ -8853,7 +9146,7 @@ ComponentHandle.prototype._compileBindings = function() {
8853
9146
  * Build ref map from the live DOM after createDOM.
8854
9147
  * @private
8855
9148
  */
8856
- ComponentHandle.prototype._collectRefs = function() {
9149
+ _chp._collectRefs = function() {
8857
9150
  this._bw_refs = {};
8858
9151
  if (!this.element) return;
8859
9152
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -8874,7 +9167,7 @@ ComponentHandle.prototype._collectRefs = function() {
8874
9167
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
8875
9168
  * @param {Element} parentEl - DOM element to mount into
8876
9169
  */
8877
- ComponentHandle.prototype.mount = function(parentEl) {
9170
+ _chp.mount = function(parentEl) {
8878
9171
  // willMount hook
8879
9172
  if (this._hooks.willMount) this._hooks.willMount(this);
8880
9173
 
@@ -8896,7 +9189,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
8896
9189
  // Register named actions in function registry
8897
9190
  var self = this;
8898
9191
  for (var actionName in this._actions) {
8899
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
9192
+ if (_hop.call(this._actions, actionName)) {
8900
9193
  var registeredName = this._bwId + '_' + actionName;
8901
9194
  (function(aName) {
8902
9195
  bw.funcRegister(function(evt) {
@@ -8915,6 +9208,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
8915
9208
  this.element = bw.createDOM(tacoForDOM);
8916
9209
  this.element._bwComponentHandle = this;
8917
9210
  this.element.setAttribute('data-bw_comp_id', this._bwId);
9211
+
9212
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
9213
+ if (this.taco.o && this.taco.o.render) {
9214
+ this.element._bw_render = this.taco.o.render;
9215
+ }
8918
9216
  if (this._userTag) {
8919
9217
  this.element.classList.add(this._userTag);
8920
9218
  }
@@ -8930,6 +9228,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
8930
9228
 
8931
9229
  this.mounted = true;
8932
9230
 
9231
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
9232
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
9233
+ for (var ci = 0; ci < childEls.length; ci++) {
9234
+ var ch = childEls[ci]._bwComponentHandle;
9235
+ if (ch && ch !== this && !ch._parent) {
9236
+ ch._parent = this;
9237
+ this._children.push(ch);
9238
+ }
9239
+ }
9240
+
8933
9241
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
8934
9242
  if (this._hooks.mounted) {
8935
9243
  if (this._hooks.mounted.length === 2) {
@@ -8938,16 +9246,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
8938
9246
  this._hooks.mounted(this);
8939
9247
  }
8940
9248
  }
9249
+
9250
+ // Invoke o.render on initial mount (if present)
9251
+ if (this.element._bw_render) {
9252
+ this.element._bw_render(this.element, this._state);
9253
+ }
8941
9254
  };
8942
9255
 
8943
9256
  /**
8944
9257
  * Prepare TACO for initial render: resolve when/each markers.
8945
9258
  * @private
8946
9259
  */
8947
- ComponentHandle.prototype._prepareTaco = function(taco) {
8948
- if (!taco || typeof taco !== 'object') return;
9260
+ _chp._prepareTaco = function(taco) {
9261
+ if (!_is(taco, 'object')) return;
8949
9262
 
8950
- if (Array.isArray(taco.c)) {
9263
+ if (_isA(taco.c)) {
8951
9264
  for (var i = taco.c.length - 1; i >= 0; i--) {
8952
9265
  var child = taco.c[i];
8953
9266
  if (child && child._bwWhen) {
@@ -8972,18 +9285,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
8972
9285
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
8973
9286
  var arr = bw._evaluatePath(this._state, eachExprStr);
8974
9287
  var items = [];
8975
- if (Array.isArray(arr)) {
9288
+ if (_isA(arr)) {
8976
9289
  for (var j = 0; j < arr.length; j++) {
8977
9290
  items.push(child.factory(arr[j], j));
8978
9291
  }
8979
9292
  }
8980
9293
  taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
8981
9294
  }
8982
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
9295
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
8983
9296
  this._prepareTaco(taco.c[i]);
8984
9297
  }
8985
9298
  }
8986
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9299
+ } else if (_is(taco.c, 'object') && taco.c.t) {
8987
9300
  this._prepareTaco(taco.c);
8988
9301
  }
8989
9302
  };
@@ -8992,12 +9305,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
8992
9305
  * Wire action name strings (in onclick etc.) to dispatch function calls.
8993
9306
  * @private
8994
9307
  */
8995
- ComponentHandle.prototype._wireActions = function(taco) {
8996
- if (!taco || typeof taco !== 'object' || !taco.t) return;
9308
+ _chp._wireActions = function(taco) {
9309
+ if (!_is(taco, 'object') || !taco.t) return;
8997
9310
  if (taco.a) {
8998
9311
  for (var key in taco.a) {
8999
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
9000
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
9312
+ if (!_hop.call(taco.a, key)) continue;
9313
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
9001
9314
  var actionName = taco.a[key];
9002
9315
  if (actionName in this._actions) {
9003
9316
  var registeredName = this._bwId + '_' + actionName;
@@ -9011,11 +9324,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
9011
9324
  }
9012
9325
  }
9013
9326
  }
9014
- if (Array.isArray(taco.c)) {
9327
+ if (_isA(taco.c)) {
9015
9328
  for (var i = 0; i < taco.c.length; i++) {
9016
9329
  this._wireActions(taco.c[i]);
9017
9330
  }
9018
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9331
+ } else if (_is(taco.c, 'object') && taco.c.t) {
9019
9332
  this._wireActions(taco.c);
9020
9333
  }
9021
9334
  };
@@ -9024,7 +9337,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
9024
9337
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
9025
9338
  * @private
9026
9339
  */
9027
- ComponentHandle.prototype._deepCloneTaco = function(taco) {
9340
+ _chp._deepCloneTaco = function(taco) {
9028
9341
  if (taco == null) return taco;
9029
9342
  // Preserve _bwWhen / _bwEach markers (contain functions)
9030
9343
  if (taco._bwWhen) {
@@ -9036,18 +9349,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
9036
9349
  if (taco._bwEach) {
9037
9350
  return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
9038
9351
  }
9039
- if (typeof taco !== 'object' || !taco.t) return taco;
9352
+ if (!_is(taco, 'object') || !taco.t) return taco;
9040
9353
  var result = { t: taco.t };
9041
9354
  if (taco.a) {
9042
9355
  result.a = {};
9043
9356
  for (var k in taco.a) {
9044
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
9357
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
9045
9358
  }
9046
9359
  }
9047
9360
  if (taco.c != null) {
9048
- if (Array.isArray(taco.c)) {
9361
+ if (_isA(taco.c)) {
9049
9362
  result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
9050
- } else if (typeof taco.c === 'object') {
9363
+ } else if (_is(taco.c, 'object')) {
9051
9364
  result.c = this._deepCloneTaco(taco.c);
9052
9365
  } else {
9053
9366
  result.c = taco.c;
@@ -9061,27 +9374,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
9061
9374
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
9062
9375
  * @private
9063
9376
  */
9064
- ComponentHandle.prototype._tacoForDOM = function(taco) {
9065
- if (!taco || typeof taco !== 'object' || !taco.t) return taco;
9377
+ _chp._tacoForDOM = function(taco) {
9378
+ if (!_is(taco, 'object') || !taco.t) return taco;
9066
9379
  var result = { t: taco.t };
9067
9380
  if (taco.a) result.a = taco.a;
9068
9381
  if (taco.c != null) {
9069
- if (Array.isArray(taco.c)) {
9382
+ if (_isA(taco.c)) {
9070
9383
  result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
9071
- } else if (typeof taco.c === 'object' && taco.c.t) {
9384
+ } else if (_is(taco.c, 'object') && taco.c.t) {
9072
9385
  result.c = this._tacoForDOM(taco.c);
9073
9386
  } else {
9074
9387
  result.c = taco.c;
9075
9388
  }
9076
9389
  }
9077
9390
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
9391
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
9392
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
9393
+ '>. Use onclick attribute or bw.component() for child interactivity.');
9394
+ }
9078
9395
  return result;
9079
9396
  };
9080
9397
 
9081
9398
  /**
9082
9399
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
9083
9400
  */
9084
- ComponentHandle.prototype.unmount = function() {
9401
+ _chp.unmount = function() {
9085
9402
  if (!this.mounted) return;
9086
9403
 
9087
9404
  // unmount hook
@@ -9116,12 +9433,23 @@ ComponentHandle.prototype.unmount = function() {
9116
9433
  /**
9117
9434
  * Destroy: unmount + clear state + unregister actions.
9118
9435
  */
9119
- ComponentHandle.prototype.destroy = function() {
9436
+ _chp.destroy = function() {
9120
9437
  // willDestroy hook
9121
9438
  if (this._hooks.willDestroy) {
9122
9439
  this._hooks.willDestroy(this);
9123
9440
  }
9124
9441
 
9442
+ // Cascade destroy to children depth-first (Bug #5)
9443
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
9444
+ this._children[ci].destroy();
9445
+ }
9446
+ this._children = [];
9447
+ if (this._parent) {
9448
+ var idx = this._parent._children.indexOf(this);
9449
+ if (idx >= 0) this._parent._children.splice(idx, 1);
9450
+ this._parent = null;
9451
+ }
9452
+
9125
9453
  this.unmount();
9126
9454
 
9127
9455
  // Unregister actions from function registry
@@ -9148,12 +9476,36 @@ ComponentHandle.prototype.destroy = function() {
9148
9476
  * Flush dirty state: resolve changed bindings and apply to DOM.
9149
9477
  * @private
9150
9478
  */
9151
- ComponentHandle.prototype._flush = function() {
9479
+ _chp._flush = function() {
9152
9480
  this._scheduled = false;
9153
- var changedKeys = Object.keys(this._dirtyKeys);
9481
+ var changedKeys = _keys(this._dirtyKeys);
9154
9482
  this._dirtyKeys = {};
9155
9483
  if (changedKeys.length === 0 || !this.mounted) return;
9156
9484
 
9485
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
9486
+ // rebuild the TACO from the factory with merged state (Bug #6)
9487
+ if (this._factory) {
9488
+ var rebuildNeeded = false;
9489
+ for (var fi = 0; fi < changedKeys.length; fi++) {
9490
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
9491
+ rebuildNeeded = true; break;
9492
+ }
9493
+ }
9494
+ if (rebuildNeeded) {
9495
+ var merged = {};
9496
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
9497
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
9498
+ this._factory.props = merged;
9499
+ var newTaco = bw.make(this._factory.type, merged);
9500
+ newTaco._bwFactory = this._factory;
9501
+ this.taco = newTaco;
9502
+ this._originalTaco = this._deepCloneTaco(newTaco);
9503
+ this._render();
9504
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
9505
+ return;
9506
+ }
9507
+ }
9508
+
9157
9509
  // willUpdate hook
9158
9510
  if (this._hooks.willUpdate) {
9159
9511
  this._hooks.willUpdate(this, changedKeys);
@@ -9192,7 +9544,7 @@ ComponentHandle.prototype._flush = function() {
9192
9544
  * Returns list of patches to apply.
9193
9545
  * @private
9194
9546
  */
9195
- ComponentHandle.prototype._resolveBindings = function(changedKeys) {
9547
+ _chp._resolveBindings = function(changedKeys) {
9196
9548
  var patches = [];
9197
9549
  for (var i = 0; i < this._bindings.length; i++) {
9198
9550
  var b = this._bindings[i];
@@ -9228,11 +9580,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
9228
9580
  * Apply patches to DOM.
9229
9581
  * @private
9230
9582
  */
9231
- ComponentHandle.prototype._applyPatches = function(patches) {
9583
+ _chp._applyPatches = function(patches) {
9232
9584
  for (var i = 0; i < patches.length; i++) {
9233
9585
  var p = patches[i];
9234
9586
  var el = this._bw_refs[p.refId];
9235
- if (!el) continue;
9587
+ if (!el) {
9588
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
9589
+ continue;
9590
+ }
9236
9591
  if (p.type === 'content') {
9237
9592
  el.textContent = p.value;
9238
9593
  } else if (p.type === 'attribute') {
@@ -9249,7 +9604,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
9249
9604
  * Resolve all bindings and apply (used for initial render).
9250
9605
  * @private
9251
9606
  */
9252
- ComponentHandle.prototype._resolveAndApplyAll = function() {
9607
+ _chp._resolveAndApplyAll = function() {
9253
9608
  var patches = [];
9254
9609
  for (var i = 0; i < this._bindings.length; i++) {
9255
9610
  var b = this._bindings[i];
@@ -9272,7 +9627,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
9272
9627
  * Full re-render for structural changes (when/each branch switches).
9273
9628
  * @private
9274
9629
  */
9275
- ComponentHandle.prototype._render = function() {
9630
+ _chp._render = function() {
9276
9631
  if (!this.element || !this.element.parentNode) return;
9277
9632
  var parent = this.element.parentNode;
9278
9633
  var nextSibling = this.element.nextSibling;
@@ -9312,7 +9667,7 @@ ComponentHandle.prototype._render = function() {
9312
9667
  * @param {string} event - Event name (e.g., 'click')
9313
9668
  * @param {Function} handler - Event handler
9314
9669
  */
9315
- ComponentHandle.prototype.on = function(event, handler) {
9670
+ _chp.on = function(event, handler) {
9316
9671
  if (this.element) {
9317
9672
  this.element.addEventListener(event, handler);
9318
9673
  }
@@ -9324,7 +9679,7 @@ ComponentHandle.prototype.on = function(event, handler) {
9324
9679
  * @param {string} event - Event name
9325
9680
  * @param {Function} handler - Handler to remove
9326
9681
  */
9327
- ComponentHandle.prototype.off = function(event, handler) {
9682
+ _chp.off = function(event, handler) {
9328
9683
  if (this.element) {
9329
9684
  this.element.removeEventListener(event, handler);
9330
9685
  }
@@ -9339,7 +9694,7 @@ ComponentHandle.prototype.off = function(event, handler) {
9339
9694
  * @param {Function} handler - Handler function
9340
9695
  * @returns {Function} Unsubscribe function
9341
9696
  */
9342
- ComponentHandle.prototype.sub = function(topic, handler) {
9697
+ _chp.sub = function(topic, handler) {
9343
9698
  var unsub = bw.sub(topic, handler);
9344
9699
  this._subs.push(unsub);
9345
9700
  return unsub;
@@ -9350,10 +9705,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
9350
9705
  * @param {string} name - Action name
9351
9706
  * @param {...*} args - Arguments passed after comp
9352
9707
  */
9353
- ComponentHandle.prototype.action = function(name) {
9708
+ _chp.action = function(name) {
9354
9709
  var fn = this._actions[name];
9355
9710
  if (!fn) {
9356
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
9711
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
9357
9712
  return;
9358
9713
  }
9359
9714
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -9365,7 +9720,7 @@ ComponentHandle.prototype.action = function(name) {
9365
9720
  * @param {string} sel - CSS selector
9366
9721
  * @returns {Element|null}
9367
9722
  */
9368
- ComponentHandle.prototype.select = function(sel) {
9723
+ _chp.select = function(sel) {
9369
9724
  return this.element ? this.element.querySelector(sel) : null;
9370
9725
  };
9371
9726
 
@@ -9374,7 +9729,7 @@ ComponentHandle.prototype.select = function(sel) {
9374
9729
  * @param {string} sel - CSS selector
9375
9730
  * @returns {Element[]}
9376
9731
  */
9377
- ComponentHandle.prototype.selectAll = function(sel) {
9732
+ _chp.selectAll = function(sel) {
9378
9733
  if (!this.element) return [];
9379
9734
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
9380
9735
  };
@@ -9385,7 +9740,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
9385
9740
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
9386
9741
  * @returns {ComponentHandle} this (for chaining)
9387
9742
  */
9388
- ComponentHandle.prototype.userTag = function(tag) {
9743
+ _chp.userTag = function(tag) {
9389
9744
  this._userTag = tag;
9390
9745
  if (this.element) {
9391
9746
  this.element.classList.add(tag);
@@ -9486,8 +9841,8 @@ bw.message = function(target, action, data) {
9486
9841
  }
9487
9842
  if (!el || !el._bwComponentHandle) return false;
9488
9843
  var comp = el._bwComponentHandle;
9489
- if (typeof comp[action] !== 'function') {
9490
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
9844
+ if (!_is(comp[action], 'function')) {
9845
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
9491
9846
  return false;
9492
9847
  }
9493
9848
  comp[action](data);
@@ -9524,7 +9879,7 @@ bw._builtinClientFunctions = {
9524
9879
  },
9525
9880
  focus: function(selector) {
9526
9881
  var el = bw._el(selector);
9527
- if (el && typeof el.focus === 'function') el.focus();
9882
+ if (el && _is(el.focus, 'function')) el.focus();
9528
9883
  },
9529
9884
  download: function(filename, content, mimeType) {
9530
9885
  if (typeof document === 'undefined') return;
@@ -9689,12 +10044,12 @@ bw.clientApply = function(msg) {
9689
10044
  } else if (type === 'remove') {
9690
10045
  var toRemove = bw._el(target);
9691
10046
  if (!toRemove) return false;
9692
- if (typeof bw.cleanup === 'function') bw.cleanup(toRemove);
10047
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
9693
10048
  toRemove.remove();
9694
10049
  return true;
9695
10050
 
9696
10051
  } else if (type === 'batch') {
9697
- if (!Array.isArray(msg.ops)) return false;
10052
+ if (!_isA(msg.ops)) return false;
9698
10053
  var allOk = true;
9699
10054
  msg.ops.forEach(function(op) {
9700
10055
  if (!bw.clientApply(op)) allOk = false;
@@ -9710,26 +10065,26 @@ bw.clientApply = function(msg) {
9710
10065
  bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
9711
10066
  return true;
9712
10067
  } catch (e) {
9713
- console.error('[bw] register error:', msg.name, e);
10068
+ _ce('[bw] register error:', msg.name, e);
9714
10069
  return false;
9715
10070
  }
9716
10071
 
9717
10072
  } else if (type === 'call') {
9718
10073
  if (!msg.name) return false;
9719
10074
  var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
9720
- if (typeof fn !== 'function') return false;
10075
+ if (!_is(fn, 'function')) return false;
9721
10076
  try {
9722
- var args = Array.isArray(msg.args) ? msg.args : [];
10077
+ var args = _isA(msg.args) ? msg.args : [];
9723
10078
  fn.apply(null, args);
9724
10079
  return true;
9725
10080
  } catch (e) {
9726
- console.error('[bw] call error:', msg.name, e);
10081
+ _ce('[bw] call error:', msg.name, e);
9727
10082
  return false;
9728
10083
  }
9729
10084
 
9730
10085
  } else if (type === 'exec') {
9731
10086
  if (!bw._allowExec) {
9732
- console.warn('[bw] exec rejected: allowExec is not enabled');
10087
+ _cw('[bw] exec rejected: allowExec is not enabled');
9733
10088
  return false;
9734
10089
  }
9735
10090
  if (!msg.code) return false;
@@ -9737,7 +10092,7 @@ bw.clientApply = function(msg) {
9737
10092
  new Function(msg.code)();
9738
10093
  return true;
9739
10094
  } catch (e) {
9740
- console.error('[bw] exec error:', e);
10095
+ _ce('[bw] exec error:', e);
9741
10096
  return false;
9742
10097
  }
9743
10098
  }
@@ -9785,7 +10140,7 @@ bw.clientConnect = function(url, opts) {
9785
10140
 
9786
10141
  function handleMessage(data) {
9787
10142
  try {
9788
- var msg = typeof data === 'string' ? bw.clientParse(data) : data;
10143
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
9789
10144
  if (onMessage) onMessage(msg);
9790
10145
  if (handlers.message) handlers.message(msg);
9791
10146
  bw.clientApply(msg);
@@ -9823,7 +10178,7 @@ bw.clientConnect = function(url, opts) {
9823
10178
  setStatus('connected');
9824
10179
  conn._pollTimer = setInterval(function() {
9825
10180
  fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
9826
- if (Array.isArray(msgs)) {
10181
+ if (_isA(msgs)) {
9827
10182
  msgs.forEach(handleMessage);
9828
10183
  } else if (msgs && msgs.type) {
9829
10184
  handleMessage(msgs);
@@ -9905,33 +10260,33 @@ bw.inspect = function(target) {
9905
10260
  el = target.element;
9906
10261
  comp = target;
9907
10262
  } else {
9908
- if (typeof target === 'string') {
10263
+ if (_is(target, 'string')) {
9909
10264
  el = bw.$(target)[0];
9910
10265
  }
9911
10266
  if (!el) {
9912
- console.warn('bw.inspect: element not found');
10267
+ _cw('bw.inspect: element not found');
9913
10268
  return null;
9914
10269
  }
9915
10270
  comp = el._bwComponentHandle;
9916
10271
  }
9917
10272
  if (!comp) {
9918
- console.log('bw.inspect: no ComponentHandle on this element');
9919
- console.log(' Tag:', el.tagName);
9920
- console.log(' Classes:', el.className);
9921
- console.log(' _bw_state:', el._bw_state || '(none)');
10273
+ _cl('bw.inspect: no ComponentHandle on this element');
10274
+ _cl(' Tag:', el.tagName);
10275
+ _cl(' Classes:', el.className);
10276
+ _cl(' _bw_state:', el._bw_state || '(none)');
9922
10277
  return null;
9923
10278
  }
9924
10279
  var deps = comp._bindings.reduce(function(s, b) {
9925
10280
  return s.concat(b.deps || []);
9926
10281
  }, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
9927
10282
  console.group('Component: ' + comp._bwId);
9928
- console.log('State:', comp._state);
9929
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
9930
- console.log('Methods:', Object.keys(comp._methods));
9931
- console.log('Actions:', Object.keys(comp._actions));
9932
- console.log('User tag:', comp._userTag || '(none)');
9933
- console.log('Mounted:', comp.mounted);
9934
- console.log('Element:', comp.element);
10283
+ _cl('State:', comp._state);
10284
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
10285
+ _cl('Methods:', _keys(comp._methods));
10286
+ _cl('Actions:', _keys(comp._actions));
10287
+ _cl('User tag:', comp._userTag || '(none)');
10288
+ _cl('Mounted:', comp.mounted);
10289
+ _cl('Element:', comp.element);
9935
10290
  console.groupEnd();
9936
10291
  return comp;
9937
10292
  };
@@ -9954,8 +10309,8 @@ bw.compile = function(taco) {
9954
10309
  // Pre-extract all binding expressions
9955
10310
  var precompiled = [];
9956
10311
  function walkExpressions(node) {
9957
- if (!node || typeof node !== 'object') return;
9958
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
10312
+ if (!_is(node, 'object')) return;
10313
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
9959
10314
  var parsed = bw._parseBindings(node.c);
9960
10315
  for (var i = 0; i < parsed.length; i++) {
9961
10316
  try {
@@ -9970,9 +10325,9 @@ bw.compile = function(taco) {
9970
10325
  }
9971
10326
  if (node.a) {
9972
10327
  for (var key in node.a) {
9973
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
10328
+ if (_hop.call(node.a, key)) {
9974
10329
  var v = node.a[key];
9975
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
10330
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
9976
10331
  var parsed2 = bw._parseBindings(v);
9977
10332
  for (var j = 0; j < parsed2.length; j++) {
9978
10333
  try {
@@ -9988,9 +10343,9 @@ bw.compile = function(taco) {
9988
10343
  }
9989
10344
  }
9990
10345
  }
9991
- if (Array.isArray(node.c)) {
10346
+ if (_isA(node.c)) {
9992
10347
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
9993
- } else if (node.c && typeof node.c === 'object' && node.c.t) {
10348
+ } else if (_is(node.c, 'object') && node.c.t) {
9994
10349
  walkExpressions(node.c);
9995
10350
  }
9996
10351
  }
@@ -10002,7 +10357,7 @@ bw.compile = function(taco) {
10002
10357
  handle._precompiledBindings = precompiled;
10003
10358
  if (initialState) {
10004
10359
  for (var k in initialState) {
10005
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
10360
+ if (_hop.call(initialState, k)) {
10006
10361
  handle._state[k] = initialState[k];
10007
10362
  }
10008
10363
  }
@@ -10033,18 +10388,18 @@ bw.compile = function(taco) {
10033
10388
  bw.css = function(rules, options = {}) {
10034
10389
  const { minify = false, pretty = !minify } = options;
10035
10390
 
10036
- if (typeof rules === 'string') return rules;
10391
+ if (_is(rules, 'string')) return rules;
10037
10392
 
10038
10393
  let css = '';
10039
10394
  const indent = pretty ? ' ' : '';
10040
10395
  const newline = pretty ? '\n' : '';
10041
10396
  const space = pretty ? ' ' : '';
10042
10397
 
10043
- if (Array.isArray(rules)) {
10398
+ if (_isA(rules)) {
10044
10399
  css = rules.map(rule => bw.css(rule, options)).join(newline);
10045
- } else if (typeof rules === 'object') {
10400
+ } else if (_is(rules, 'object')) {
10046
10401
  Object.entries(rules).forEach(([selector, styles]) => {
10047
- if (typeof styles === 'object' && !Array.isArray(styles)) {
10402
+ if (_is(styles, 'object')) {
10048
10403
  // Handle @media, @keyframes, @supports — recurse into nested block
10049
10404
  if (selector.charAt(0) === '@') {
10050
10405
  const inner = bw.css(styles, options);
@@ -10093,7 +10448,7 @@ bw.css = function(rules, options = {}) {
10093
10448
  */
10094
10449
  bw.injectCSS = function(css, options = {}) {
10095
10450
  if (!bw._isBrowser) {
10096
- console.warn('bw.injectCSS requires a DOM environment');
10451
+ _cw('bw.injectCSS requires a DOM environment');
10097
10452
  return null;
10098
10453
  }
10099
10454
 
@@ -10110,7 +10465,7 @@ bw.injectCSS = function(css, options = {}) {
10110
10465
  }
10111
10466
 
10112
10467
  // Convert CSS if needed
10113
- const cssStr = typeof css === 'string' ? css : bw.css(css, options);
10468
+ const cssStr = _is(css, 'string') ? css : bw.css(css, options);
10114
10469
 
10115
10470
  // Set or append CSS
10116
10471
  if (append && styleEl.textContent) {
@@ -10140,7 +10495,7 @@ bw.s = function() {
10140
10495
  var result = {};
10141
10496
  for (var i = 0; i < arguments.length; i++) {
10142
10497
  var arg = arguments[i];
10143
- if (arg && typeof arg === 'object') Object.assign(result, arg);
10498
+ if (_is(arg, 'object')) Object.assign(result, arg);
10144
10499
  }
10145
10500
  return result;
10146
10501
  };
@@ -10263,7 +10618,7 @@ bw.u = {
10263
10618
  bw.responsive = function(selector, breakpoints) {
10264
10619
  var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
10265
10620
  var parts = [];
10266
- Object.keys(breakpoints).forEach(function(key) {
10621
+ _keys(breakpoints).forEach(function(key) {
10267
10622
  var rules = {};
10268
10623
  if (key === 'base') {
10269
10624
  rules[selector] = breakpoints[key];
@@ -10335,18 +10690,18 @@ if (bw._isBrowser) {
10335
10690
  if (!selector) return [];
10336
10691
 
10337
10692
  // Already an array
10338
- if (Array.isArray(selector)) return selector;
10693
+ if (_isA(selector)) return selector;
10339
10694
 
10340
10695
  // Single element
10341
10696
  if (selector.nodeType) return [selector];
10342
10697
 
10343
10698
  // NodeList or HTMLCollection
10344
- if (selector.length !== undefined && typeof selector !== 'string') {
10699
+ if (selector.length !== undefined && !_is(selector, 'string')) {
10345
10700
  return Array.from(selector);
10346
10701
  }
10347
10702
 
10348
10703
  // CSS selector string
10349
- if (typeof selector === 'string') {
10704
+ if (_is(selector, 'string')) {
10350
10705
  return Array.from(document.querySelectorAll(selector));
10351
10706
  }
10352
10707
 
@@ -10850,7 +11205,7 @@ bw.makeTable = function(config) {
10850
11205
 
10851
11206
  // Auto-detect columns if not provided
10852
11207
  const cols = columns || (data.length > 0
10853
- ? Object.keys(data[0]).map(key => ({ key, label: key }))
11208
+ ? _keys(data[0]).map(key => ({ key, label: key }))
10854
11209
  : []);
10855
11210
 
10856
11211
  // Current sort state
@@ -10865,7 +11220,7 @@ bw.makeTable = function(config) {
10865
11220
  const bVal = b[currentSortColumn];
10866
11221
 
10867
11222
  // Handle different types
10868
- if (typeof aVal === 'number' && typeof bVal === 'number') {
11223
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
10869
11224
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
10870
11225
  }
10871
11226
 
@@ -10975,7 +11330,7 @@ bw.makeTable = function(config) {
10975
11330
  bw.makeTableFromArray = function(config) {
10976
11331
  const { data = [], headerRow = true, columns, ...rest } = config;
10977
11332
 
10978
- if (!Array.isArray(data) || data.length === 0) {
11333
+ if (!_isA(data) || data.length === 0) {
10979
11334
  return bw.makeTable({ data: [], columns: columns || [], ...rest });
10980
11335
  }
10981
11336
 
@@ -11057,7 +11412,7 @@ bw.makeBarChart = function(config) {
11057
11412
  className = ''
11058
11413
  } = config;
11059
11414
 
11060
- if (!Array.isArray(data) || data.length === 0) {
11415
+ if (!_isA(data) || data.length === 0) {
11061
11416
  return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
11062
11417
  }
11063
11418
 
@@ -11206,7 +11561,7 @@ bw._componentRegistry = new Map();
11206
11561
  */
11207
11562
  bw.render = function(element, position, taco) {
11208
11563
  // Get target element
11209
- const targetEl = typeof element === 'string'
11564
+ const targetEl = _is(element, 'string')
11210
11565
  ? document.querySelector(element)
11211
11566
  : element;
11212
11567
 
@@ -11356,7 +11711,7 @@ bw.render = function(element, position, taco) {
11356
11711
  setContent(content) {
11357
11712
  this._taco.c = content;
11358
11713
  if (this.element) {
11359
- if (typeof content === 'string') {
11714
+ if (_is(content, 'string')) {
11360
11715
  this.element.textContent = content;
11361
11716
  } else {
11362
11717
  // Re-render for complex content