pulse-js-framework 1.4.4 → 1.4.6

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.
package/README.md CHANGED
@@ -13,6 +13,7 @@ A declarative DOM framework with CSS selector-based structure and reactive pulsa
13
13
  - **No Build Required** - Works directly in the browser
14
14
  - **Lightweight** - Minimal footprint, maximum performance
15
15
  - **Router & Store** - Built-in SPA routing and state management
16
+ - **Hot Module Replacement** - Full HMR with state preservation
16
17
  - **Mobile Apps** - Build native Android & iOS apps (zero dependencies)
17
18
  - **TypeScript Support** - Full type definitions for IDE autocomplete
18
19
 
@@ -280,6 +281,57 @@ const store = createStore({
280
281
  store.user.set({ name: 'John' });
281
282
  ```
282
283
 
284
+ ### HMR (Hot Module Replacement)
285
+
286
+ Pulse supports full HMR with state preservation during development:
287
+
288
+ ```javascript
289
+ import { createHMRContext } from 'pulse-js-framework/runtime/hmr';
290
+
291
+ const hmr = createHMRContext(import.meta.url);
292
+
293
+ // State preserved across HMR updates
294
+ const count = hmr.preservePulse('count', 0);
295
+ const items = hmr.preservePulse('items', []);
296
+
297
+ // Effects tracked for automatic cleanup
298
+ hmr.setup(() => {
299
+ effect(() => {
300
+ document.title = `Count: ${count.get()}`;
301
+ });
302
+ });
303
+
304
+ // Accept HMR updates
305
+ hmr.accept();
306
+ ```
307
+
308
+ **HMR Features:**
309
+ - `preservePulse(key, value)` - Create pulses that survive module replacement
310
+ - `setup(callback)` - Execute with effect tracking for cleanup
311
+ - Automatic event listener cleanup (no accumulation)
312
+ - Works with Vite dev server
313
+
314
+ ### Logger
315
+
316
+ ```javascript
317
+ import { createLogger, setLogLevel, LogLevel } from 'pulse-js-framework/runtime/logger';
318
+
319
+ // Create a namespaced logger
320
+ const log = createLogger('MyComponent');
321
+
322
+ log.info('Component initialized'); // [MyComponent] Component initialized
323
+ log.warn('Deprecated method used');
324
+ log.error('Failed to load', error);
325
+ log.debug('Detailed info'); // Only shown at DEBUG level
326
+
327
+ // Set global log level
328
+ setLogLevel(LogLevel.DEBUG); // SILENT, ERROR, WARN, INFO, DEBUG
329
+
330
+ // Child loggers for sub-namespaces
331
+ const childLog = log.child('Validation');
332
+ childLog.info('Validating'); // [MyComponent:Validation] Validating
333
+ ```
334
+
283
335
  ## CLI Commands
284
336
 
285
337
  ```bash
@@ -399,6 +451,28 @@ onNativeReady(({ platform }) => {
399
451
 
400
452
  **Available APIs:** Storage, Device Info, Network Status, Toast, Vibration, Clipboard, App Lifecycle
401
453
 
454
+ ## VSCode Extension
455
+
456
+ Pulse includes a VSCode extension for `.pulse` files with syntax highlighting and snippets.
457
+
458
+ ### Installation
459
+
460
+ ```bash
461
+ # Windows (PowerShell)
462
+ cd vscode-extension
463
+ powershell -ExecutionPolicy Bypass -File install.ps1
464
+
465
+ # macOS/Linux
466
+ cd vscode-extension
467
+ bash install.sh
468
+ ```
469
+
470
+ Then restart VSCode. You'll get:
471
+ - Syntax highlighting for `.pulse` files
472
+ - Code snippets (`page`, `state`, `view`, `@click`, etc.)
473
+ - Bracket matching and auto-closing
474
+ - Comment toggling (Ctrl+/)
475
+
402
476
  ## TypeScript Support
403
477
 
404
478
  Pulse includes full TypeScript definitions for IDE autocomplete and type checking:
package/cli/index.js CHANGED
@@ -7,11 +7,14 @@
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname, join, resolve } from 'path';
9
9
  import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync } from 'fs';
10
+ import { log } from './logger.js';
10
11
 
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = dirname(__filename);
13
14
 
14
- const VERSION = '1.4.3';
15
+ // Version - read dynamically from package.json
16
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
17
+ const VERSION = pkg.version;
15
18
 
16
19
  // Command handlers
17
20
  const commands = {
@@ -38,8 +41,8 @@ async function main() {
38
41
  if (command in commands) {
39
42
  await commands[command](args.slice(1));
40
43
  } else {
41
- console.error(`Unknown command: ${command}`);
42
- console.log('Run "pulse help" for usage information.');
44
+ log.error(`Unknown command: ${command}`);
45
+ log.info('Run "pulse help" for usage information.');
43
46
  process.exit(1);
44
47
  }
45
48
  }
@@ -48,7 +51,7 @@ async function main() {
48
51
  * Show help message
49
52
  */
50
53
  function showHelp() {
51
- console.log(`
54
+ log.info(`
52
55
  Pulse Framework CLI v${VERSION}
53
56
 
54
57
  Usage: pulse <command> [options]
@@ -102,7 +105,7 @@ Documentation: https://github.com/vincenthirtz/pulse-js-framework
102
105
  * Show version
103
106
  */
104
107
  function showVersion() {
105
- console.log(`Pulse Framework v${VERSION}`);
108
+ log.info(`Pulse Framework v${VERSION}`);
106
109
  }
107
110
 
108
111
  /**
@@ -112,19 +115,19 @@ async function createProject(args) {
112
115
  const projectName = args[0];
113
116
 
114
117
  if (!projectName) {
115
- console.error('Please provide a project name.');
116
- console.log('Usage: pulse create <project-name>');
118
+ log.error('Please provide a project name.');
119
+ log.info('Usage: pulse create <project-name>');
117
120
  process.exit(1);
118
121
  }
119
122
 
120
123
  const projectPath = resolve(process.cwd(), projectName);
121
124
 
122
125
  if (existsSync(projectPath)) {
123
- console.error(`Directory "${projectName}" already exists.`);
126
+ log.error(`Directory "${projectName}" already exists.`);
124
127
  process.exit(1);
125
128
  }
126
129
 
127
- console.log(`Creating new Pulse project: ${projectName}`);
130
+ log.info(`Creating new Pulse project: ${projectName}`);
128
131
 
129
132
  // Create project structure
130
133
  mkdirSync(projectPath);
@@ -281,7 +284,7 @@ dist
281
284
 
282
285
  writeFileSync(join(projectPath, '.gitignore'), gitignore);
283
286
 
284
- console.log(`
287
+ log.info(`
285
288
  Project created successfully!
286
289
 
287
290
  Next steps:
@@ -297,7 +300,7 @@ Happy coding with Pulse!
297
300
  * Run development server
298
301
  */
299
302
  async function runDev(args) {
300
- console.log('Starting Pulse development server...');
303
+ log.info('Starting Pulse development server...');
301
304
 
302
305
  // Use dynamic import for the dev server module
303
306
  const { startDevServer } = await import('./dev.js');
@@ -308,7 +311,7 @@ async function runDev(args) {
308
311
  * Build for production
309
312
  */
310
313
  async function runBuild(args) {
311
- console.log('Building Pulse project for production...');
314
+ log.info('Building Pulse project for production...');
312
315
 
313
316
  const { buildProject } = await import('./build.js');
314
317
  await buildProject(args);
@@ -318,7 +321,7 @@ async function runBuild(args) {
318
321
  * Preview production build
319
322
  */
320
323
  async function runPreview(args) {
321
- console.log('Starting Pulse preview server...');
324
+ log.info('Starting Pulse preview server...');
322
325
 
323
326
  const { previewBuild } = await import('./build.js');
324
327
  await previewBuild(args);
@@ -363,13 +366,13 @@ async function compileFile(args) {
363
366
  const inputFile = args[0];
364
367
 
365
368
  if (!inputFile) {
366
- console.error('Please provide a file to compile.');
367
- console.log('Usage: pulse compile <file.pulse>');
369
+ log.error('Please provide a file to compile.');
370
+ log.info('Usage: pulse compile <file.pulse>');
368
371
  process.exit(1);
369
372
  }
370
373
 
371
374
  if (!existsSync(inputFile)) {
372
- console.error(`File not found: ${inputFile}`);
375
+ log.error(`File not found: ${inputFile}`);
373
376
  process.exit(1);
374
377
  }
375
378
 
@@ -381,11 +384,11 @@ async function compileFile(args) {
381
384
  if (result.success) {
382
385
  const outputFile = inputFile.replace(/\.pulse$/, '.js');
383
386
  writeFileSync(outputFile, result.code);
384
- console.log(`Compiled: ${inputFile} -> ${outputFile}`);
387
+ log.info(`Compiled: ${inputFile} -> ${outputFile}`);
385
388
  } else {
386
- console.error('Compilation failed:');
389
+ log.error('Compilation failed:');
387
390
  for (const error of result.errors) {
388
- console.error(` ${error.message}`);
391
+ log.error(` ${error.message}`);
389
392
  }
390
393
  process.exit(1);
391
394
  }
@@ -393,6 +396,6 @@ async function compileFile(args) {
393
396
 
394
397
  // Run main
395
398
  main().catch(error => {
396
- console.error('Error:', error.message);
399
+ log.error('Error:', error.message);
397
400
  process.exit(1);
398
401
  });
package/cli/logger.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Pulse CLI Logger
3
+ * Lightweight logger for CLI tools with support for verbose mode
4
+ * @module pulse-cli/logger
5
+ */
6
+
7
+ /** @type {boolean} */
8
+ let verboseMode = false;
9
+
10
+ /**
11
+ * Enable or disable verbose mode for debug output
12
+ * @param {boolean} enabled - Whether to enable verbose mode
13
+ * @returns {void}
14
+ * @example
15
+ * setVerbose(true);
16
+ * log.debug('This will now be shown');
17
+ */
18
+ export function setVerbose(enabled) {
19
+ verboseMode = enabled;
20
+ }
21
+
22
+ /**
23
+ * Check if verbose mode is currently enabled
24
+ * @returns {boolean} True if verbose mode is enabled
25
+ * @example
26
+ * if (isVerbose()) {
27
+ * // Perform additional logging
28
+ * }
29
+ */
30
+ export function isVerbose() {
31
+ return verboseMode;
32
+ }
33
+
34
+ /**
35
+ * CLI Logger object with console-like API
36
+ * @namespace log
37
+ */
38
+ export const log = {
39
+ /**
40
+ * Log an info message (always shown)
41
+ * @param {...*} args - Values to log
42
+ * @returns {void}
43
+ * @example
44
+ * log.info('Starting server on port', 3000);
45
+ */
46
+ info(...args) {
47
+ console.log(...args);
48
+ },
49
+
50
+ /**
51
+ * Log a success message (always shown)
52
+ * @param {...*} args - Values to log
53
+ * @returns {void}
54
+ * @example
55
+ * log.success('Build completed successfully!');
56
+ */
57
+ success(...args) {
58
+ console.log(...args);
59
+ },
60
+
61
+ /**
62
+ * Log a warning message
63
+ * @param {...*} args - Values to log
64
+ * @returns {void}
65
+ * @example
66
+ * log.warn('Deprecated feature used');
67
+ */
68
+ warn(...args) {
69
+ console.warn(...args);
70
+ },
71
+
72
+ /**
73
+ * Log an error message
74
+ * @param {...*} args - Values to log
75
+ * @returns {void}
76
+ * @example
77
+ * log.error('Failed to compile:', error.message);
78
+ */
79
+ error(...args) {
80
+ console.error(...args);
81
+ },
82
+
83
+ /**
84
+ * Log a debug message (only shown in verbose mode)
85
+ * @param {...*} args - Values to log
86
+ * @returns {void}
87
+ * @example
88
+ * log.debug('Processing file:', filename);
89
+ */
90
+ debug(...args) {
91
+ if (verboseMode) {
92
+ console.log('[debug]', ...args);
93
+ }
94
+ },
95
+
96
+ /**
97
+ * Log a verbose message (only shown in verbose mode)
98
+ * @param {...*} args - Values to log
99
+ * @returns {void}
100
+ * @example
101
+ * log.verbose('Additional details:', data);
102
+ */
103
+ verbose(...args) {
104
+ if (verboseMode) {
105
+ console.log(...args);
106
+ }
107
+ },
108
+
109
+ /**
110
+ * Print a blank line for spacing
111
+ * @returns {void}
112
+ * @example
113
+ * log.info('Section 1');
114
+ * log.newline();
115
+ * log.info('Section 2');
116
+ */
117
+ newline() {
118
+ console.log();
119
+ }
120
+ };
121
+
122
+ export default log;
package/index.js CHANGED
@@ -4,14 +4,20 @@
4
4
  * A declarative DOM framework with CSS selector-based structure
5
5
  */
6
6
 
7
+ import { readFileSync } from 'fs';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname, join } from 'path';
10
+
7
11
  // Runtime exports
8
12
  export * from './runtime/index.js';
9
13
 
10
14
  // Compiler exports
11
15
  export { compile, parse, tokenize } from './compiler/index.js';
12
16
 
13
- // Version
14
- export const VERSION = '1.4.3';
17
+ // Version - read dynamically from package.json
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
20
+ export const VERSION = pkg.version;
15
21
 
16
22
  // Default export
17
23
  export default {
@@ -69,22 +69,30 @@ export default function pulsePlugin(options = {}) {
69
69
  /**
70
70
  * Handle hot module replacement
71
71
  */
72
- handleHotUpdate({ file, server }) {
72
+ handleHotUpdate({ file, server, modules }) {
73
73
  if (file.endsWith('.pulse')) {
74
74
  console.log(`[Pulse] HMR update: ${file}`);
75
75
 
76
- // Invalidate the module
76
+ // Invalidate the module in Vite's module graph
77
77
  const module = server.moduleGraph.getModuleById(file);
78
78
  if (module) {
79
79
  server.moduleGraph.invalidateModule(module);
80
80
  }
81
81
 
82
- // Send full reload for now
83
- // In a more advanced implementation, we could do partial updates
82
+ // Send HMR update instead of full reload
83
+ // The module will handle its own state preservation via hmrRuntime
84
84
  server.ws.send({
85
- type: 'full-reload',
86
- path: '*'
85
+ type: 'update',
86
+ updates: [{
87
+ type: 'js-update',
88
+ path: file,
89
+ acceptedPath: file,
90
+ timestamp: Date.now()
91
+ }]
87
92
  });
93
+
94
+ // Return empty array to prevent Vite's default HMR handling
95
+ return [];
88
96
  }
89
97
  },
90
98
 
@@ -117,6 +125,14 @@ export default function pulsePlugin(options = {}) {
117
125
  */
118
126
  export const hmrRuntime = `
119
127
  if (import.meta.hot) {
128
+ // Cleanup effects before module replacement
129
+ import.meta.hot.dispose(() => {
130
+ import('pulse-js-framework/runtime/pulse').then(m => {
131
+ m.disposeModule(import.meta.url);
132
+ });
133
+ });
134
+
135
+ // Accept HMR updates
120
136
  import.meta.hot.accept((newModule) => {
121
137
  if (newModule) {
122
138
  // Re-render with new module
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -17,6 +17,14 @@
17
17
  "types": "./types/index.d.ts",
18
18
  "default": "./runtime/index.js"
19
19
  },
20
+ "./runtime/pulse": {
21
+ "types": "./types/pulse.d.ts",
22
+ "default": "./runtime/pulse.js"
23
+ },
24
+ "./runtime/dom": {
25
+ "types": "./types/dom.d.ts",
26
+ "default": "./runtime/dom.js"
27
+ },
20
28
  "./runtime/router": {
21
29
  "types": "./types/router.d.ts",
22
30
  "default": "./runtime/router.js"
@@ -25,9 +33,30 @@
25
33
  "types": "./types/store.d.ts",
26
34
  "default": "./runtime/store.js"
27
35
  },
28
- "./runtime/*": "./runtime/*.js",
29
- "./compiler": "./compiler/index.js",
30
- "./vite": "./loader/vite-plugin.js"
36
+ "./runtime/native": {
37
+ "types": "./types/index.d.ts",
38
+ "default": "./runtime/native.js"
39
+ },
40
+ "./runtime/logger": {
41
+ "types": "./types/logger.d.ts",
42
+ "default": "./runtime/logger.js"
43
+ },
44
+ "./runtime/hmr": {
45
+ "default": "./runtime/hmr.js"
46
+ },
47
+ "./compiler": {
48
+ "types": "./types/index.d.ts",
49
+ "default": "./compiler/index.js"
50
+ },
51
+ "./compiler/lexer": "./compiler/lexer.js",
52
+ "./compiler/parser": "./compiler/parser.js",
53
+ "./compiler/transformer": "./compiler/transformer.js",
54
+ "./vite": {
55
+ "types": "./types/index.d.ts",
56
+ "default": "./loader/vite-plugin.js"
57
+ },
58
+ "./mobile": "./mobile/bridge/pulse-native.js",
59
+ "./package.json": "./package.json"
31
60
  },
32
61
  "files": [
33
62
  "index.js",
@@ -41,16 +70,18 @@
41
70
  "LICENSE"
42
71
  ],
43
72
  "scripts": {
44
- "test": "npm run test:compiler && npm run test:pulse && npm run test:dom && npm run test:router && npm run test:store && npm run test:lint && npm run test:format && npm run test:analyze",
73
+ "test": "npm run test:compiler && npm run test:pulse && npm run test:dom && npm run test:router && npm run test:store && npm run test:hmr && npm run test:lint && npm run test:format && npm run test:analyze",
45
74
  "test:compiler": "node test/compiler.test.js",
46
75
  "test:pulse": "node test/pulse.test.js",
47
76
  "test:dom": "node test/dom.test.js",
48
77
  "test:router": "node test/router.test.js",
49
78
  "test:store": "node test/store.test.js",
79
+ "test:hmr": "node test/hmr.test.js",
50
80
  "test:lint": "node test/lint.test.js",
51
81
  "test:format": "node test/format.test.js",
52
82
  "test:analyze": "node test/analyze.test.js",
53
- "build:netlify": "node scripts/build-netlify.js"
83
+ "build:netlify": "node scripts/build-netlify.js",
84
+ "version": "node scripts/sync-version.js"
54
85
  },
55
86
  "keywords": [
56
87
  "framework",
package/runtime/dom.js CHANGED
@@ -6,6 +6,13 @@
6
6
  */
7
7
 
8
8
  import { effect, pulse, batch, onCleanup } from './pulse.js';
9
+ import { loggers } from './logger.js';
10
+
11
+ const log = loggers.dom;
12
+
13
+ // Selector cache for parseSelector
14
+ const selectorCache = new Map();
15
+ const SELECTOR_CACHE_MAX = 500;
9
16
 
10
17
  // Lifecycle tracking
11
18
  let mountCallbacks = [];
@@ -38,6 +45,7 @@ export function onUnmount(fn) {
38
45
  /**
39
46
  * Parse a CSS selector-like string into element configuration
40
47
  * Supports: tag, #id, .class, [attr=value]
48
+ * Results are cached for performance.
41
49
  *
42
50
  * Examples:
43
51
  * "div" -> { tag: "div" }
@@ -47,6 +55,22 @@ export function onUnmount(fn) {
47
55
  * "input[type=text][placeholder=Name]" -> { tag: "input", attrs: { type: "text", placeholder: "Name" } }
48
56
  */
49
57
  export function parseSelector(selector) {
58
+ if (!selector || selector === '') {
59
+ return { tag: 'div', id: null, classes: [], attrs: {} };
60
+ }
61
+
62
+ // Check cache first
63
+ const cached = selectorCache.get(selector);
64
+ if (cached) {
65
+ // Return a shallow copy to prevent mutation
66
+ return {
67
+ tag: cached.tag,
68
+ id: cached.id,
69
+ classes: [...cached.classes],
70
+ attrs: { ...cached.attrs }
71
+ };
72
+ }
73
+
50
74
  const config = {
51
75
  tag: 'div',
52
76
  id: null,
@@ -54,30 +78,30 @@ export function parseSelector(selector) {
54
78
  attrs: {}
55
79
  };
56
80
 
57
- if (!selector || selector === '') return config;
81
+ let remaining = selector;
58
82
 
59
83
  // Match tag name at the start
60
- const tagMatch = selector.match(/^([a-zA-Z][a-zA-Z0-9-]*)/);
84
+ const tagMatch = remaining.match(/^([a-zA-Z][a-zA-Z0-9-]*)/);
61
85
  if (tagMatch) {
62
86
  config.tag = tagMatch[1];
63
- selector = selector.slice(tagMatch[0].length);
87
+ remaining = remaining.slice(tagMatch[0].length);
64
88
  }
65
89
 
66
90
  // Match ID
67
- const idMatch = selector.match(/#([a-zA-Z][a-zA-Z0-9-_]*)/);
91
+ const idMatch = remaining.match(/#([a-zA-Z][a-zA-Z0-9-_]*)/);
68
92
  if (idMatch) {
69
93
  config.id = idMatch[1];
70
- selector = selector.replace(idMatch[0], '');
94
+ remaining = remaining.replace(idMatch[0], '');
71
95
  }
72
96
 
73
97
  // Match classes
74
- const classMatches = selector.matchAll(/\.([a-zA-Z][a-zA-Z0-9-_]*)/g);
98
+ const classMatches = remaining.matchAll(/\.([a-zA-Z][a-zA-Z0-9-_]*)/g);
75
99
  for (const match of classMatches) {
76
100
  config.classes.push(match[1]);
77
101
  }
78
102
 
79
103
  // Match attributes
80
- const attrMatches = selector.matchAll(/\[([a-zA-Z][a-zA-Z0-9-_]*)(?:=([^\]]+))?\]/g);
104
+ const attrMatches = remaining.matchAll(/\[([a-zA-Z][a-zA-Z0-9-_]*)(?:=([^\]]+))?\]/g);
81
105
  for (const match of attrMatches) {
82
106
  const key = match[1];
83
107
  let value = match[2] || '';
@@ -89,7 +113,21 @@ export function parseSelector(selector) {
89
113
  config.attrs[key] = value;
90
114
  }
91
115
 
92
- return config;
116
+ // Cache the result (with size limit to prevent memory leaks)
117
+ if (selectorCache.size >= SELECTOR_CACHE_MAX) {
118
+ // Remove oldest entry (first key)
119
+ const firstKey = selectorCache.keys().next().value;
120
+ selectorCache.delete(firstKey);
121
+ }
122
+ selectorCache.set(selector, config);
123
+
124
+ // Return a copy
125
+ return {
126
+ tag: config.tag,
127
+ id: config.id,
128
+ classes: [...config.classes],
129
+ attrs: { ...config.attrs }
130
+ };
93
131
  }
94
132
 
95
133
  /**
@@ -262,6 +300,12 @@ export function style(element, prop, getValue) {
262
300
  */
263
301
  export function on(element, event, handler, options) {
264
302
  element.addEventListener(event, handler, options);
303
+
304
+ // Auto-cleanup: remove listener when effect is disposed (HMR support)
305
+ onCleanup(() => {
306
+ element.removeEventListener(event, handler, options);
307
+ });
308
+
265
309
  return element;
266
310
  }
267
311
 
@@ -523,7 +567,7 @@ export function component(setup) {
523
567
  try {
524
568
  cb();
525
569
  } catch (e) {
526
- console.error('Mount callback error:', e);
570
+ log.error('Mount callback error:', e);
527
571
  }
528
572
  }
529
573
  });
@@ -559,7 +603,7 @@ export function portal(children, target) {
559
603
  : target;
560
604
 
561
605
  if (!resolvedTarget) {
562
- console.warn('Portal target not found:', target);
606
+ log.warn('Portal target not found:', target);
563
607
  return document.createComment('portal-target-not-found');
564
608
  }
565
609
 
@@ -653,7 +697,7 @@ export function errorBoundary(children, fallback) {
653
697
  marker.parentNode?.insertBefore(fragment, marker.nextSibling);
654
698
  }
655
699
  } catch (e) {
656
- console.error('Error in component:', e);
700
+ log.error('Error in component:', e);
657
701
  error.set(e);
658
702
  // Re-render with error
659
703
  if (!hasError) {