Update jqhtml packages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-12 16:40:23 +00:00
parent 29b1abc0a1
commit 5295013f73
86 changed files with 826 additions and 711 deletions

0
node_modules/@jqhtml/core/dist/boot.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/boot.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/component-registry.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/component-registry.d.ts.map generated vendored Executable file → Normal file
View File

36
node_modules/@jqhtml/core/dist/component.d.ts generated vendored Executable file → Normal file
View File

@@ -54,6 +54,8 @@ export declare class Jqhtml_Component {
private _skip_render_and_ready;
private _skip_ready;
private _has_rendered;
private _load_queue;
private __has_custom_on_load;
constructor(element?: any, args?: Record<string, any>);
/**
* Protect lifecycle methods from manual invocation
@@ -77,6 +79,7 @@ export declare class Jqhtml_Component {
/**
* Check if on_load() is overridden in a subclass
* Used to skip the load phase entirely for components that don't fetch data
* Returns cached value set in _boot() for O(1) performance
* @private
*/
private _has_on_load;
@@ -124,12 +127,43 @@ export declare class Jqhtml_Component {
*/
create(): void;
/**
* Load phase - Fetch data from APIs
* Load phase - Fetch asynchronous data
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*
* Key design: on_load() runs on a DETACHED proxy with a clone of this.data.
* The real component's this.data is not touched during on_load execution.
* After on_load completes, the result is applied via a sequential queue.
*
* @private - Internal lifecycle method, not for external use
*/
_load(): Promise<void>;
/**
* Execute on_load() on a fully detached proxy.
*
* The proxy has:
* - A CLONE of this.data (from __initial_data_snapshot)
* - Read-only access to this.args
* - No access to anything else (this.$, this.sid, etc.)
*
* This ensures on_load runs completely isolated from the component instance.
*
* @param use_load_coordinator - If true, register with Load_Coordinator for deduplication
* @returns The resulting data from the proxy after on_load completes
* @private
*/
private _execute_on_load_detached;
/**
* Apply the result of on_load() to this.data via the sequential queue.
*
* Multiple _load calls can execute on_load in parallel, but this method
* ensures that setting this.data happens in FIFO order.
*
* @param result_data - The data returned from _execute_on_load_detached
* @param data_before_load - JSON string of this.data before on_load (for change detection), or null
* @private
*/
private _apply_load_result;
/**
* Ready phase - Component fully initialized
* Called bottom-up (children before parent)

2
node_modules/@jqhtml/core/dist/component.d.ts.map generated vendored Executable file → Normal file
View File

@@ -1 +1 @@
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}

0
node_modules/@jqhtml/core/dist/debug-entry.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/debug-entry.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/debug.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/debug.d.ts.map generated vendored Executable file → Normal file
View File

475
node_modules/@jqhtml/core/dist/index.cjs generated vendored Executable file → Normal file
View File

@@ -2138,6 +2138,17 @@ class Jqhtml_Component {
// rendered event - fires once after the synchronous render chain completes
// (after on_load's re-render if applicable, or after first render if no on_load)
this._has_rendered = false;
// Sequential queue for setting this.data after on_load completes
// Multiple _load calls can run on_load in parallel, but setting this.data is serialized
this._load_queue = Promise.resolve();
// Cached flag: true if this component has a custom on_load() override
// Set in constructor (prototype chain is established before constructor runs)
// Used to skip expensive cache/load operations for static components
this.__has_custom_on_load = false;
// Detect custom on_load() override immediately
// Prototype chain is set up before constructor runs, so this.on_load
// correctly resolves to child class override if one exists
this.__has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -2318,14 +2329,11 @@ class Jqhtml_Component {
/**
* Check if on_load() is overridden in a subclass
* Used to skip the load phase entirely for components that don't fetch data
* Returns cached value set in _boot() for O(1) performance
* @private
*/
_has_on_load() {
// Get this instance's on_load implementation
const impl = lifecycle_impls.get(this)?.['on_load'] || this.on_load;
// Compare to base class implementation
// If they're the same function reference, it wasn't overridden
return impl !== Jqhtml_Component.prototype.on_load;
return this.__has_custom_on_load;
}
/**
* Boot - Start the full component lifecycle
@@ -2341,6 +2349,7 @@ class Jqhtml_Component {
return;
this._booted = true;
// Protect lifecycle methods from manual invocation (must happen after subclass constructor)
// Note: __has_custom_on_load is already set in constructor
this._protect_lifecycle_methods();
await this._lifecycle_manager.boot_component(this);
}
@@ -2693,98 +2702,102 @@ class Jqhtml_Component {
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
// Don't await - on_create MUST be sync. The warning is enough.
}
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
// Components without on_load() don't fetch data, so nothing to cache or restore
if (this.__has_custom_on_load) {
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
}
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
}
}
}
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
// Freeze this.data after on_create() - only on_load() can modify it now
this.__data_frozen = true;
this._ready_state = 1;
@@ -2794,9 +2807,14 @@ class Jqhtml_Component {
this.trigger('create');
}
/**
* Load phase - Fetch data from APIs
* Load phase - Fetch asynchronous data
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*
* Key design: on_load() runs on a DETACHED proxy with a clone of this.data.
* The real component's this.data is not touched during on_load execution.
* After on_load completes, the result is applied via a sequential queue.
*
* @private - Internal lifecycle method, not for external use
*/
async _load() {
@@ -2812,14 +2830,6 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// Restore this.data to initial state from snapshot (skip on first load)
// This ensures on_load() always starts with clean state
const is_first_load = this._ready_state < 2;
if (!is_first_load && this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
// Unfreeze this.data so on_load() can modify it
this.__data_frozen = false;
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
@@ -2839,6 +2849,8 @@ class Jqhtml_Component {
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// Store cache key for later use
this._cache_key = cache_key;
// If cache_key is null, args are not serializable - skip load deduplication and caching
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
@@ -2848,9 +2860,10 @@ class Jqhtml_Component {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
}
// Execute on_load() with authorization, without deduplication or caching
await this._call_lifecycle('on_load');
this.__data_frozen = true;
// Execute on_load on detached proxy without deduplication
const result_data = await this._execute_on_load_detached();
// Apply result via sequential queue
await this._apply_load_result(result_data, null);
return;
}
// Store "before" snapshot for comparison after on_load()
@@ -2884,10 +2897,39 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// This component is a leader - execute on_load() normally
// This component is a leader - execute on_load() on detached proxy
if (window.jqhtml?.debug?.verbose) {
console.log(`[Load Deduplication] Component ${this._cid} (${this.component_name()}) is the leader`, { args: this.args });
}
// Execute on_load on detached proxy with Load_Coordinator registration
const result_data = await this._execute_on_load_detached(true);
// Apply result via sequential queue
await this._apply_load_result(result_data, data_before_load);
}
/**
* Execute on_load() on a fully detached proxy.
*
* The proxy has:
* - A CLONE of this.data (from __initial_data_snapshot)
* - Read-only access to this.args
* - No access to anything else (this.$, this.sid, etc.)
*
* This ensures on_load runs completely isolated from the component instance.
*
* @param use_load_coordinator - If true, register with Load_Coordinator for deduplication
* @returns The resulting data from the proxy after on_load completes
* @private
*/
async _execute_on_load_detached(use_load_coordinator = false) {
// Clone this.data from the snapshot captured after on_create()
const data_clone = this.__initial_data_snapshot
? JSON.parse(JSON.stringify(this.__initial_data_snapshot))
: {};
// Create a detached context object that on_load will operate on
const detached_context = {
args: this.args, // Read-only reference to real args
data: data_clone // Cloned data - modifications stay isolated
};
// Capture args state before on_load() for validation
let argsBeforeLoad = null;
try {
@@ -2898,17 +2940,21 @@ class Jqhtml_Component {
}
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
// Create restricted proxy to prevent DOM access during on_load()
const restricted_this = new Proxy(this, {
// Create restricted proxy that operates on the detached context
const component_name = this.component_name();
const restricted_this = new Proxy(detached_context, {
get(target, prop) {
// Only allow access to this.args and this.data
if (prop === 'args' || prop === 'data') {
return target[prop];
// Only allow access to args and data
if (prop === 'args') {
return target.args;
}
if (prop === 'data') {
return target.data;
}
// Block everything else
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to access this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to access this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY access:\n` +
` - this.args (read during on_load, modify before/after)\n` +
` - this.args (read-only)\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. All other component functionality should happen in other lifecycle methods.\n\n` +
`FIX:\n` +
@@ -2919,14 +2965,14 @@ class Jqhtml_Component {
`on_load() may only access this.args and this.data.`);
},
set(target, prop, value) {
// Only allow setting this.data
// Only allow setting data
if (prop === 'data') {
target[prop] = value;
target.data = value;
return true;
}
// Block setting this.args
// Block setting args
if (prop === 'args') {
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.args during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.args during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: this.args is component state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.\n\n` +
@@ -2937,7 +2983,7 @@ class Jqhtml_Component {
`Modify this.args in other lifecycle methods, not inside on_load().`);
}
// Block setting any other properties
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. Setting properties on the component instance should happen in other lifecycle methods.\n\n` +
@@ -2948,35 +2994,36 @@ class Jqhtml_Component {
`Only this.data can be modified in on_load().`);
}
});
// Create promise for this on_load() call with restricted this context
// Create promise for this on_load() call
const on_load_promise = (async () => {
try {
// TODO: Implement proper error handling for on_load() failures
// - Should errors trigger re-render with error state?
// - Should errors clear cached data?
// - Should errors reset state machine flags (next_reload_force_refresh)?
// - Should partial data be preserved or rolled back?
// - Should followers be notified differently based on error type?
await this._call_lifecycle('on_load', restricted_this);
}
catch (error) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
if (use_load_coordinator) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
}
throw error;
}
})();
// Register as leader and get cleanup function
const complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
// If using Load_Coordinator, register as leader
let complete_coordination = null;
if (use_load_coordinator) {
complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
}
try {
await on_load_promise;
}
finally {
// Always clear loading flag and complete coordination
// Always clear loading flag
this.__loading = false;
complete_coordination();
// Note: this.data stays unfrozen until after normalization below
// Complete coordination if we registered
if (complete_coordination) {
complete_coordination();
}
}
// Validate that on_load() only modified this.data
// Validate that on_load() didn't somehow modify this.args (shouldn't be possible with new proxy)
let argsAfterLoad = null;
try {
argsAfterLoad = JSON.stringify(this.args);
@@ -2984,44 +3031,59 @@ class Jqhtml_Component {
catch (error) {
// Args contain circular references - skip validation
}
// Check if args were modified (skip if args are non-serializable)
if (argsBeforeLoad !== null && argsAfterLoad !== null && argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data.\n\n` +
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" somehow modified this.args during on_load().\n` +
`This should not be possible with the detached proxy. Please report this bug.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Modify this.args in on_create() or other lifecycle methods, not in on_load().\n` +
`this.args stores state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.`);
`After: ${argsAfterLoad}`);
}
// NOTE: We previously checked for new properties being added to the component instance
// during on_load() and warned about them. However, this validation was removed because:
// 1. The restricted proxy already throws errors if code tries to set properties during on_load()
// 2. The validation was producing false positives for properties set during template render
// (templates can legitimately set this.* properties in code blocks like <% this._tabs = [...] %>)
// 3. Properties set via the restricted proxy throw immediately, making post-hoc validation redundant
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
// (restored from cache). Unregistered class instances become plain objects immediately,
// so developers catch missing class registrations during development rather than
// only after a page reload when data comes from cache.
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
// Return the data from the detached context
return detached_context.data;
}
/**
* Apply the result of on_load() to this.data via the sequential queue.
*
* Multiple _load calls can execute on_load in parallel, but this method
* ensures that setting this.data happens in FIFO order.
*
* @param result_data - The data returned from _execute_on_load_detached
* @param data_before_load - JSON string of this.data before on_load (for change detection), or null
* @private
*/
async _apply_load_result(result_data, data_before_load) {
// Queue this operation - wait for any earlier _load calls to finish setting data
const my_turn = this._load_queue;
let resolve_my_turn;
this._load_queue = new Promise((resolve) => {
resolve_my_turn = resolve;
});
// Wait for our turn
await my_turn;
try {
// Now it's our turn to set this.data
// Unfreeze, set data, freeze
this.__data_frozen = false;
this.data = result_data;
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
}
}
}
// Freeze this.data after normalization completes
this.__data_frozen = true;
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
if (this._is_dynamic) {
if (this._cache_key) {
// Freeze this.data
this.__data_frozen = true;
// Calculate if data changed
const data_after_load = JSON.stringify(this.data);
const data_changed = data_before_load !== null && data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
if (this._is_dynamic && this._cache_key) {
if (cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
@@ -3037,12 +3099,16 @@ class Jqhtml_Component {
}
}
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
finally {
// Signal next in queue
resolve_my_turn();
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
/**
* Ready phase - Component fully initialized
@@ -3305,6 +3371,18 @@ class Jqhtml_Component {
if (this._stopped)
return;
this._log_lifecycle('reload', 'start');
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
// Just re-render with current data and call on_ready
if (!this.__has_custom_on_load) {
// Reset state machine
this.next_reload_force_refresh = null;
this._render();
await this._wait_for_children_ready();
await this._call_lifecycle('on_ready');
this.trigger('ready');
this._log_lifecycle('reload', 'complete (no on_load)');
return;
}
// STEP 1: Cache check (if args changed)
let rendered_from_cache = false;
let data_before_load = null;
@@ -3374,72 +3452,16 @@ class Jqhtml_Component {
}
// Capture data state before on_load for comparison
data_before_load = JSON.stringify(this.data);
// STEP 2: Call on_load() to fetch fresh data
// Unfreeze this.data so we can restore snapshot and on_load() can modify it
this.__data_frozen = false;
// Restore this.data to initial state from snapshot (if not first load)
if (this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
try {
await this._call_lifecycle('on_load');
}
finally {
// Freeze this.data after on_load() completes
this.__data_frozen = true;
}
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// (Same as in _load() - ensures hot/cold cache parity)
const reload_cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (reload_cache_mode === 'data') {
this.__data_frozen = false;
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
this.__data_frozen = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load() in reload()`, { data: this.data });
}
}
// Check if data changed during on_load() - if so, update cache (but not if empty)
// STEP 2: Call on_load() on detached proxy
// This uses the same detached execution as _load() - on_load runs isolated
// and result is applied via sequential queue
const result_data = await this._execute_on_load_detached(false); // false = don't use Load_Coordinator
// Apply result via sequential queue
// This handles normalization, caching, and setting _is_dynamic
await this._apply_load_result(result_data, data_before_load);
// Re-read data state after apply (for render decision below)
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
if (data_changed && data_after_load !== '{}') {
// Check if component implements cache_id() for custom cache key
let cache_key = null;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
cache_key = null;
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
}
// Write to cache based on mode
if (cache_key !== null) {
this._cache_key = cache_key;
if (reload_cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) will cache HTML after ready in reload()`, { cache_key });
}
}
else {
// Data cache mode (default) - write this.data to cache
Jqhtml_Local_Storage.set(cache_key, this.data);
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cached data after on_load() in reload()`, { cache_key, data: this.data });
}
}
}
}
// STEP 3: Conditionally re-render (with refresh() support)
// Read force_refresh from state machine (default true)
const force_refresh = this.next_reload_force_refresh !== null ? this.next_reload_force_refresh : true;
@@ -3573,6 +3595,11 @@ class Jqhtml_Component {
this._used_cached_html = false;
return true;
}
// OPTIMIZATION: If no custom on_load(), data cannot change, skip serialization
// Data is frozen after on_create() and only unfrozen during on_load()
if (!this.__has_custom_on_load) {
return false;
}
// Compare current data state with data state before initial render
const currentDataState = JSON.stringify(this.data);
const dataChanged = this._data_before_render !== currentDataState;
@@ -4817,7 +4844,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.3.20';
const version = '2.3.21';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/index.cjs.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
node_modules/@jqhtml/core/dist/index.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/index.d.ts.map generated vendored Executable file → Normal file
View File

475
node_modules/@jqhtml/core/dist/index.js generated vendored Executable file → Normal file
View File

@@ -2134,6 +2134,17 @@ class Jqhtml_Component {
// rendered event - fires once after the synchronous render chain completes
// (after on_load's re-render if applicable, or after first render if no on_load)
this._has_rendered = false;
// Sequential queue for setting this.data after on_load completes
// Multiple _load calls can run on_load in parallel, but setting this.data is serialized
this._load_queue = Promise.resolve();
// Cached flag: true if this component has a custom on_load() override
// Set in constructor (prototype chain is established before constructor runs)
// Used to skip expensive cache/load operations for static components
this.__has_custom_on_load = false;
// Detect custom on_load() override immediately
// Prototype chain is set up before constructor runs, so this.on_load
// correctly resolves to child class override if one exists
this.__has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -2314,14 +2325,11 @@ class Jqhtml_Component {
/**
* Check if on_load() is overridden in a subclass
* Used to skip the load phase entirely for components that don't fetch data
* Returns cached value set in _boot() for O(1) performance
* @private
*/
_has_on_load() {
// Get this instance's on_load implementation
const impl = lifecycle_impls.get(this)?.['on_load'] || this.on_load;
// Compare to base class implementation
// If they're the same function reference, it wasn't overridden
return impl !== Jqhtml_Component.prototype.on_load;
return this.__has_custom_on_load;
}
/**
* Boot - Start the full component lifecycle
@@ -2337,6 +2345,7 @@ class Jqhtml_Component {
return;
this._booted = true;
// Protect lifecycle methods from manual invocation (must happen after subclass constructor)
// Note: __has_custom_on_load is already set in constructor
this._protect_lifecycle_methods();
await this._lifecycle_manager.boot_component(this);
}
@@ -2689,98 +2698,102 @@ class Jqhtml_Component {
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
// Don't await - on_create MUST be sync. The warning is enough.
}
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
// Components without on_load() don't fetch data, so nothing to cache or restore
if (this.__has_custom_on_load) {
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
}
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
}
}
}
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
// Freeze this.data after on_create() - only on_load() can modify it now
this.__data_frozen = true;
this._ready_state = 1;
@@ -2790,9 +2803,14 @@ class Jqhtml_Component {
this.trigger('create');
}
/**
* Load phase - Fetch data from APIs
* Load phase - Fetch asynchronous data
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*
* Key design: on_load() runs on a DETACHED proxy with a clone of this.data.
* The real component's this.data is not touched during on_load execution.
* After on_load completes, the result is applied via a sequential queue.
*
* @private - Internal lifecycle method, not for external use
*/
async _load() {
@@ -2808,14 +2826,6 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// Restore this.data to initial state from snapshot (skip on first load)
// This ensures on_load() always starts with clean state
const is_first_load = this._ready_state < 2;
if (!is_first_load && this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
// Unfreeze this.data so on_load() can modify it
this.__data_frozen = false;
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
@@ -2835,6 +2845,8 @@ class Jqhtml_Component {
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// Store cache key for later use
this._cache_key = cache_key;
// If cache_key is null, args are not serializable - skip load deduplication and caching
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
@@ -2844,9 +2856,10 @@ class Jqhtml_Component {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
}
// Execute on_load() with authorization, without deduplication or caching
await this._call_lifecycle('on_load');
this.__data_frozen = true;
// Execute on_load on detached proxy without deduplication
const result_data = await this._execute_on_load_detached();
// Apply result via sequential queue
await this._apply_load_result(result_data, null);
return;
}
// Store "before" snapshot for comparison after on_load()
@@ -2880,10 +2893,39 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// This component is a leader - execute on_load() normally
// This component is a leader - execute on_load() on detached proxy
if (window.jqhtml?.debug?.verbose) {
console.log(`[Load Deduplication] Component ${this._cid} (${this.component_name()}) is the leader`, { args: this.args });
}
// Execute on_load on detached proxy with Load_Coordinator registration
const result_data = await this._execute_on_load_detached(true);
// Apply result via sequential queue
await this._apply_load_result(result_data, data_before_load);
}
/**
* Execute on_load() on a fully detached proxy.
*
* The proxy has:
* - A CLONE of this.data (from __initial_data_snapshot)
* - Read-only access to this.args
* - No access to anything else (this.$, this.sid, etc.)
*
* This ensures on_load runs completely isolated from the component instance.
*
* @param use_load_coordinator - If true, register with Load_Coordinator for deduplication
* @returns The resulting data from the proxy after on_load completes
* @private
*/
async _execute_on_load_detached(use_load_coordinator = false) {
// Clone this.data from the snapshot captured after on_create()
const data_clone = this.__initial_data_snapshot
? JSON.parse(JSON.stringify(this.__initial_data_snapshot))
: {};
// Create a detached context object that on_load will operate on
const detached_context = {
args: this.args, // Read-only reference to real args
data: data_clone // Cloned data - modifications stay isolated
};
// Capture args state before on_load() for validation
let argsBeforeLoad = null;
try {
@@ -2894,17 +2936,21 @@ class Jqhtml_Component {
}
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
// Create restricted proxy to prevent DOM access during on_load()
const restricted_this = new Proxy(this, {
// Create restricted proxy that operates on the detached context
const component_name = this.component_name();
const restricted_this = new Proxy(detached_context, {
get(target, prop) {
// Only allow access to this.args and this.data
if (prop === 'args' || prop === 'data') {
return target[prop];
// Only allow access to args and data
if (prop === 'args') {
return target.args;
}
if (prop === 'data') {
return target.data;
}
// Block everything else
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to access this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to access this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY access:\n` +
` - this.args (read during on_load, modify before/after)\n` +
` - this.args (read-only)\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. All other component functionality should happen in other lifecycle methods.\n\n` +
`FIX:\n` +
@@ -2915,14 +2961,14 @@ class Jqhtml_Component {
`on_load() may only access this.args and this.data.`);
},
set(target, prop, value) {
// Only allow setting this.data
// Only allow setting data
if (prop === 'data') {
target[prop] = value;
target.data = value;
return true;
}
// Block setting this.args
// Block setting args
if (prop === 'args') {
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.args during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.args during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: this.args is component state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.\n\n` +
@@ -2933,7 +2979,7 @@ class Jqhtml_Component {
`Modify this.args in other lifecycle methods, not inside on_load().`);
}
// Block setting any other properties
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. Setting properties on the component instance should happen in other lifecycle methods.\n\n` +
@@ -2944,35 +2990,36 @@ class Jqhtml_Component {
`Only this.data can be modified in on_load().`);
}
});
// Create promise for this on_load() call with restricted this context
// Create promise for this on_load() call
const on_load_promise = (async () => {
try {
// TODO: Implement proper error handling for on_load() failures
// - Should errors trigger re-render with error state?
// - Should errors clear cached data?
// - Should errors reset state machine flags (next_reload_force_refresh)?
// - Should partial data be preserved or rolled back?
// - Should followers be notified differently based on error type?
await this._call_lifecycle('on_load', restricted_this);
}
catch (error) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
if (use_load_coordinator) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
}
throw error;
}
})();
// Register as leader and get cleanup function
const complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
// If using Load_Coordinator, register as leader
let complete_coordination = null;
if (use_load_coordinator) {
complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
}
try {
await on_load_promise;
}
finally {
// Always clear loading flag and complete coordination
// Always clear loading flag
this.__loading = false;
complete_coordination();
// Note: this.data stays unfrozen until after normalization below
// Complete coordination if we registered
if (complete_coordination) {
complete_coordination();
}
}
// Validate that on_load() only modified this.data
// Validate that on_load() didn't somehow modify this.args (shouldn't be possible with new proxy)
let argsAfterLoad = null;
try {
argsAfterLoad = JSON.stringify(this.args);
@@ -2980,44 +3027,59 @@ class Jqhtml_Component {
catch (error) {
// Args contain circular references - skip validation
}
// Check if args were modified (skip if args are non-serializable)
if (argsBeforeLoad !== null && argsAfterLoad !== null && argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data.\n\n` +
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" somehow modified this.args during on_load().\n` +
`This should not be possible with the detached proxy. Please report this bug.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Modify this.args in on_create() or other lifecycle methods, not in on_load().\n` +
`this.args stores state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.`);
`After: ${argsAfterLoad}`);
}
// NOTE: We previously checked for new properties being added to the component instance
// during on_load() and warned about them. However, this validation was removed because:
// 1. The restricted proxy already throws errors if code tries to set properties during on_load()
// 2. The validation was producing false positives for properties set during template render
// (templates can legitimately set this.* properties in code blocks like <% this._tabs = [...] %>)
// 3. Properties set via the restricted proxy throw immediately, making post-hoc validation redundant
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
// (restored from cache). Unregistered class instances become plain objects immediately,
// so developers catch missing class registrations during development rather than
// only after a page reload when data comes from cache.
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
// Return the data from the detached context
return detached_context.data;
}
/**
* Apply the result of on_load() to this.data via the sequential queue.
*
* Multiple _load calls can execute on_load in parallel, but this method
* ensures that setting this.data happens in FIFO order.
*
* @param result_data - The data returned from _execute_on_load_detached
* @param data_before_load - JSON string of this.data before on_load (for change detection), or null
* @private
*/
async _apply_load_result(result_data, data_before_load) {
// Queue this operation - wait for any earlier _load calls to finish setting data
const my_turn = this._load_queue;
let resolve_my_turn;
this._load_queue = new Promise((resolve) => {
resolve_my_turn = resolve;
});
// Wait for our turn
await my_turn;
try {
// Now it's our turn to set this.data
// Unfreeze, set data, freeze
this.__data_frozen = false;
this.data = result_data;
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
}
}
}
// Freeze this.data after normalization completes
this.__data_frozen = true;
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
if (this._is_dynamic) {
if (this._cache_key) {
// Freeze this.data
this.__data_frozen = true;
// Calculate if data changed
const data_after_load = JSON.stringify(this.data);
const data_changed = data_before_load !== null && data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
if (this._is_dynamic && this._cache_key) {
if (cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
@@ -3033,12 +3095,16 @@ class Jqhtml_Component {
}
}
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
finally {
// Signal next in queue
resolve_my_turn();
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
/**
* Ready phase - Component fully initialized
@@ -3301,6 +3367,18 @@ class Jqhtml_Component {
if (this._stopped)
return;
this._log_lifecycle('reload', 'start');
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
// Just re-render with current data and call on_ready
if (!this.__has_custom_on_load) {
// Reset state machine
this.next_reload_force_refresh = null;
this._render();
await this._wait_for_children_ready();
await this._call_lifecycle('on_ready');
this.trigger('ready');
this._log_lifecycle('reload', 'complete (no on_load)');
return;
}
// STEP 1: Cache check (if args changed)
let rendered_from_cache = false;
let data_before_load = null;
@@ -3370,72 +3448,16 @@ class Jqhtml_Component {
}
// Capture data state before on_load for comparison
data_before_load = JSON.stringify(this.data);
// STEP 2: Call on_load() to fetch fresh data
// Unfreeze this.data so we can restore snapshot and on_load() can modify it
this.__data_frozen = false;
// Restore this.data to initial state from snapshot (if not first load)
if (this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
try {
await this._call_lifecycle('on_load');
}
finally {
// Freeze this.data after on_load() completes
this.__data_frozen = true;
}
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// (Same as in _load() - ensures hot/cold cache parity)
const reload_cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (reload_cache_mode === 'data') {
this.__data_frozen = false;
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
this.__data_frozen = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load() in reload()`, { data: this.data });
}
}
// Check if data changed during on_load() - if so, update cache (but not if empty)
// STEP 2: Call on_load() on detached proxy
// This uses the same detached execution as _load() - on_load runs isolated
// and result is applied via sequential queue
const result_data = await this._execute_on_load_detached(false); // false = don't use Load_Coordinator
// Apply result via sequential queue
// This handles normalization, caching, and setting _is_dynamic
await this._apply_load_result(result_data, data_before_load);
// Re-read data state after apply (for render decision below)
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
if (data_changed && data_after_load !== '{}') {
// Check if component implements cache_id() for custom cache key
let cache_key = null;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
cache_key = null;
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
}
// Write to cache based on mode
if (cache_key !== null) {
this._cache_key = cache_key;
if (reload_cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) will cache HTML after ready in reload()`, { cache_key });
}
}
else {
// Data cache mode (default) - write this.data to cache
Jqhtml_Local_Storage.set(cache_key, this.data);
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cached data after on_load() in reload()`, { cache_key, data: this.data });
}
}
}
}
// STEP 3: Conditionally re-render (with refresh() support)
// Read force_refresh from state machine (default true)
const force_refresh = this.next_reload_force_refresh !== null ? this.next_reload_force_refresh : true;
@@ -3569,6 +3591,11 @@ class Jqhtml_Component {
this._used_cached_html = false;
return true;
}
// OPTIMIZATION: If no custom on_load(), data cannot change, skip serialization
// Data is frozen after on_create() and only unfrozen during on_load()
if (!this.__has_custom_on_load) {
return false;
}
// Compare current data state with data state before initial render
const currentDataState = JSON.stringify(this.data);
const dataChanged = this._data_before_render !== currentDataState;
@@ -4813,7 +4840,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.3.20';
const version = '2.3.21';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/index.js.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
node_modules/@jqhtml/core/dist/instruction-processor.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/instruction-processor.d.ts.map generated vendored Executable file → Normal file
View File

477
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js generated vendored Executable file → Normal file
View File

@@ -1,5 +1,5 @@
/**
* JQHTML Core v2.3.20
* JQHTML Core v2.3.21
* (c) 2025 JQHTML Team
* Released under the MIT License
*/
@@ -2139,6 +2139,17 @@ class Jqhtml_Component {
// rendered event - fires once after the synchronous render chain completes
// (after on_load's re-render if applicable, or after first render if no on_load)
this._has_rendered = false;
// Sequential queue for setting this.data after on_load completes
// Multiple _load calls can run on_load in parallel, but setting this.data is serialized
this._load_queue = Promise.resolve();
// Cached flag: true if this component has a custom on_load() override
// Set in constructor (prototype chain is established before constructor runs)
// Used to skip expensive cache/load operations for static components
this.__has_custom_on_load = false;
// Detect custom on_load() override immediately
// Prototype chain is set up before constructor runs, so this.on_load
// correctly resolves to child class override if one exists
this.__has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -2319,14 +2330,11 @@ class Jqhtml_Component {
/**
* Check if on_load() is overridden in a subclass
* Used to skip the load phase entirely for components that don't fetch data
* Returns cached value set in _boot() for O(1) performance
* @private
*/
_has_on_load() {
// Get this instance's on_load implementation
const impl = lifecycle_impls.get(this)?.['on_load'] || this.on_load;
// Compare to base class implementation
// If they're the same function reference, it wasn't overridden
return impl !== Jqhtml_Component.prototype.on_load;
return this.__has_custom_on_load;
}
/**
* Boot - Start the full component lifecycle
@@ -2342,6 +2350,7 @@ class Jqhtml_Component {
return;
this._booted = true;
// Protect lifecycle methods from manual invocation (must happen after subclass constructor)
// Note: __has_custom_on_load is already set in constructor
this._protect_lifecycle_methods();
await this._lifecycle_manager.boot_component(this);
}
@@ -2694,98 +2703,102 @@ class Jqhtml_Component {
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
// Don't await - on_create MUST be sync. The warning is enough.
}
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
// OPTIMIZATION: Skip cache operations and snapshot if no custom on_load()
// Components without on_load() don't fetch data, so nothing to cache or restore
if (this.__has_custom_on_load) {
// CACHE CHECK - Read from cache based on cache mode ('data' or 'html')
// This happens after on_create() but before render, allowing instant first render
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
catch (error) {
// cache_id() threw error - disable caching
uncacheable_property = 'cache_id()';
}
}
else {
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// If cache_key is null, caching disabled
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
if (uncacheable_property) {
this.$.attr('data-nocache', uncacheable_property);
}
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
}
// Don't return - continue to snapshot this.data for on_load restoration
}
else {
// Store cache key for later use
this._cache_key = cache_key;
// Get cache mode
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache ${cache_mode}] Component ${this._cid} (${this.component_name()}) checking cache in create()`, { cache_key, cache_mode, has_cache_key_set: Jqhtml_Local_Storage.has_cache_key() });
}
if (cache_mode === 'html') {
// HTML cache mode - check for cached HTML to inject on first render
const html_cache_key = `${cache_key}::html`;
const cached_html = Jqhtml_Local_Storage.get(html_cache_key);
if (cached_html !== null && typeof cached_html === 'string') {
// Store cached HTML for injection in _render()
this._cached_html = cached_html;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) found cached HTML`, { cache_key: html_cache_key, html_length: cached_html.length });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key: html_cache_key });
}
}
// Warn if use_cached_data is set in html cache mode - it has no effect
if (this.args.use_cached_data === true) {
console.warn(`[JQHTML] Component "${this.component_name()}" has use_cached_data=true but cache mode is 'html'.\n` +
`use_cached_data only applies to 'data' cache mode. In 'html' mode, the entire rendered HTML is cached.\n` +
`The use_cached_data flag will be ignored.`);
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
// Data cache mode (default) - check for cached data to hydrate this.data
const cached_data = Jqhtml_Local_Storage.get(cache_key);
if (cached_data !== null && typeof cached_data === 'object') {
// Hydrate this.data with cached data
this.data = cached_data;
// If use_cached_data=true, skip on_load() entirely - use cached data as final data
if (this.args.use_cached_data === true) {
this._use_cached_data_hit = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) using cached data (use_cached_data=true, skipping on_load)`, { cache_key, data: cached_data });
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) hydrated from cache`, { cache_key, data: cached_data });
}
}
}
else {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cache miss`, { cache_key });
}
}
}
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
}
// Snapshot this.data after on_create() completes
// This will be restored before each on_load() execution to reset state
this.__initial_data_snapshot = JSON.parse(JSON.stringify(this.data));
// Freeze this.data after on_create() - only on_load() can modify it now
this.__data_frozen = true;
this._ready_state = 1;
@@ -2795,9 +2808,14 @@ class Jqhtml_Component {
this.trigger('create');
}
/**
* Load phase - Fetch data from APIs
* Load phase - Fetch asynchronous data
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*
* Key design: on_load() runs on a DETACHED proxy with a clone of this.data.
* The real component's this.data is not touched during on_load execution.
* After on_load completes, the result is applied via a sequential queue.
*
* @private - Internal lifecycle method, not for external use
*/
async _load() {
@@ -2813,14 +2831,6 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// Restore this.data to initial state from snapshot (skip on first load)
// This ensures on_load() always starts with clean state
const is_first_load = this._ready_state < 2;
if (!is_first_load && this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
// Unfreeze this.data so on_load() can modify it
this.__data_frozen = false;
// Check if component implements cache_id() for custom cache key
let cache_key = null;
let uncacheable_property;
@@ -2840,6 +2850,8 @@ class Jqhtml_Component {
cache_key = result.key;
uncacheable_property = result.uncacheable_property;
}
// Store cache key for later use
this._cache_key = cache_key;
// If cache_key is null, args are not serializable - skip load deduplication and caching
if (cache_key === null) {
// Set data-nocache attribute for debugging (shows which property prevented caching)
@@ -2849,9 +2861,10 @@ class Jqhtml_Component {
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
}
// Execute on_load() with authorization, without deduplication or caching
await this._call_lifecycle('on_load');
this.__data_frozen = true;
// Execute on_load on detached proxy without deduplication
const result_data = await this._execute_on_load_detached();
// Apply result via sequential queue
await this._apply_load_result(result_data, null);
return;
}
// Store "before" snapshot for comparison after on_load()
@@ -2885,10 +2898,39 @@ class Jqhtml_Component {
this.trigger('load');
return;
}
// This component is a leader - execute on_load() normally
// This component is a leader - execute on_load() on detached proxy
if (window.jqhtml?.debug?.verbose) {
console.log(`[Load Deduplication] Component ${this._cid} (${this.component_name()}) is the leader`, { args: this.args });
}
// Execute on_load on detached proxy with Load_Coordinator registration
const result_data = await this._execute_on_load_detached(true);
// Apply result via sequential queue
await this._apply_load_result(result_data, data_before_load);
}
/**
* Execute on_load() on a fully detached proxy.
*
* The proxy has:
* - A CLONE of this.data (from __initial_data_snapshot)
* - Read-only access to this.args
* - No access to anything else (this.$, this.sid, etc.)
*
* This ensures on_load runs completely isolated from the component instance.
*
* @param use_load_coordinator - If true, register with Load_Coordinator for deduplication
* @returns The resulting data from the proxy after on_load completes
* @private
*/
async _execute_on_load_detached(use_load_coordinator = false) {
// Clone this.data from the snapshot captured after on_create()
const data_clone = this.__initial_data_snapshot
? JSON.parse(JSON.stringify(this.__initial_data_snapshot))
: {};
// Create a detached context object that on_load will operate on
const detached_context = {
args: this.args, // Read-only reference to real args
data: data_clone // Cloned data - modifications stay isolated
};
// Capture args state before on_load() for validation
let argsBeforeLoad = null;
try {
@@ -2899,17 +2941,21 @@ class Jqhtml_Component {
}
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
// Create restricted proxy to prevent DOM access during on_load()
const restricted_this = new Proxy(this, {
// Create restricted proxy that operates on the detached context
const component_name = this.component_name();
const restricted_this = new Proxy(detached_context, {
get(target, prop) {
// Only allow access to this.args and this.data
if (prop === 'args' || prop === 'data') {
return target[prop];
// Only allow access to args and data
if (prop === 'args') {
return target.args;
}
if (prop === 'data') {
return target.data;
}
// Block everything else
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to access this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to access this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY access:\n` +
` - this.args (read during on_load, modify before/after)\n` +
` - this.args (read-only)\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. All other component functionality should happen in other lifecycle methods.\n\n` +
`FIX:\n` +
@@ -2920,14 +2966,14 @@ class Jqhtml_Component {
`on_load() may only access this.args and this.data.`);
},
set(target, prop, value) {
// Only allow setting this.data
// Only allow setting data
if (prop === 'data') {
target[prop] = value;
target.data = value;
return true;
}
// Block setting this.args
// Block setting args
if (prop === 'args') {
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.args during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.args during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: this.args is component state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.\n\n` +
@@ -2938,7 +2984,7 @@ class Jqhtml_Component {
`Modify this.args in other lifecycle methods, not inside on_load().`);
}
// Block setting any other properties
console.error(`[JQHTML] ERROR: Component "${target.component_name()}" attempted to modify this.${String(prop)} during on_load().\n\n` +
console.error(`[JQHTML] ERROR: Component "${component_name}" attempted to modify this.${String(prop)} during on_load().\n\n` +
`RESTRICTION: on_load() may ONLY modify:\n` +
` - this.data (read/write)\n\n` +
`WHY: on_load() is for data fetching only. Setting properties on the component instance should happen in other lifecycle methods.\n\n` +
@@ -2949,35 +2995,36 @@ class Jqhtml_Component {
`Only this.data can be modified in on_load().`);
}
});
// Create promise for this on_load() call with restricted this context
// Create promise for this on_load() call
const on_load_promise = (async () => {
try {
// TODO: Implement proper error handling for on_load() failures
// - Should errors trigger re-render with error state?
// - Should errors clear cached data?
// - Should errors reset state machine flags (next_reload_force_refresh)?
// - Should partial data be preserved or rolled back?
// - Should followers be notified differently based on error type?
await this._call_lifecycle('on_load', restricted_this);
}
catch (error) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
if (use_load_coordinator) {
// Handle error and notify coordinator
Load_Coordinator.handle_leader_error(this, error);
}
throw error;
}
})();
// Register as leader and get cleanup function
const complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
// If using Load_Coordinator, register as leader
let complete_coordination = null;
if (use_load_coordinator) {
complete_coordination = Load_Coordinator.register_leader(this, on_load_promise);
}
try {
await on_load_promise;
}
finally {
// Always clear loading flag and complete coordination
// Always clear loading flag
this.__loading = false;
complete_coordination();
// Note: this.data stays unfrozen until after normalization below
// Complete coordination if we registered
if (complete_coordination) {
complete_coordination();
}
}
// Validate that on_load() only modified this.data
// Validate that on_load() didn't somehow modify this.args (shouldn't be possible with new proxy)
let argsAfterLoad = null;
try {
argsAfterLoad = JSON.stringify(this.args);
@@ -2985,44 +3032,59 @@ class Jqhtml_Component {
catch (error) {
// Args contain circular references - skip validation
}
// Check if args were modified (skip if args are non-serializable)
if (argsBeforeLoad !== null && argsAfterLoad !== null && argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data.\n\n` +
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" somehow modified this.args during on_load().\n` +
`This should not be possible with the detached proxy. Please report this bug.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Modify this.args in on_create() or other lifecycle methods, not in on_load().\n` +
`this.args stores state that on_load() depends on. Modifying it inside on_load() creates circular dependencies.`);
`After: ${argsAfterLoad}`);
}
// NOTE: We previously checked for new properties being added to the component instance
// during on_load() and warned about them. However, this validation was removed because:
// 1. The restricted proxy already throws errors if code tries to set properties during on_load()
// 2. The validation was producing false positives for properties set during template render
// (templates can legitimately set this.* properties in code blocks like <% this._tabs = [...] %>)
// 3. Properties set via the restricted proxy throw immediately, making post-hoc validation redundant
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
// (restored from cache). Unregistered class instances become plain objects immediately,
// so developers catch missing class registrations during development rather than
// only after a page reload when data comes from cache.
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
// Return the data from the detached context
return detached_context.data;
}
/**
* Apply the result of on_load() to this.data via the sequential queue.
*
* Multiple _load calls can execute on_load in parallel, but this method
* ensures that setting this.data happens in FIFO order.
*
* @param result_data - The data returned from _execute_on_load_detached
* @param data_before_load - JSON string of this.data before on_load (for change detection), or null
* @private
*/
async _apply_load_result(result_data, data_before_load) {
// Queue this operation - wait for any earlier _load calls to finish setting data
const my_turn = this._load_queue;
let resolve_my_turn;
this._load_queue = new Promise((resolve) => {
resolve_my_turn = resolve;
});
// Wait for our turn
await my_turn;
try {
// Now it's our turn to set this.data
// Unfreeze, set data, freeze
this.__data_frozen = false;
this.data = result_data;
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// This ensures "hot" data (fresh from on_load) behaves identically to "cold" data
const cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (cache_mode === 'data') {
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load()`, { data: this.data });
}
}
}
// Freeze this.data after normalization completes
this.__data_frozen = true;
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
if (this._is_dynamic) {
if (this._cache_key) {
// Freeze this.data
this.__data_frozen = true;
// Calculate if data changed
const data_after_load = JSON.stringify(this.data);
const data_changed = data_before_load !== null && data_after_load !== data_before_load;
// Track if component is "dynamic" (this.data changed during on_load)
// Used by HTML cache mode for synchronization - static parents don't block children
this._is_dynamic = data_changed && data_after_load !== '{}';
// CACHE WRITE - If data changed during on_load(), write to cache based on mode
if (this._is_dynamic && this._cache_key) {
if (cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
@@ -3038,12 +3100,16 @@ class Jqhtml_Component {
}
}
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
finally {
// Signal next in queue
resolve_my_turn();
}
this._ready_state = 2;
this._update_debug_attrs();
this._log_lifecycle('load', 'complete');
// Emit lifecycle event
this.trigger('load');
}
/**
* Ready phase - Component fully initialized
@@ -3306,6 +3372,18 @@ class Jqhtml_Component {
if (this._stopped)
return;
this._log_lifecycle('reload', 'start');
// OPTIMIZATION: If no custom on_load(), skip data fetching entirely
// Just re-render with current data and call on_ready
if (!this.__has_custom_on_load) {
// Reset state machine
this.next_reload_force_refresh = null;
this._render();
await this._wait_for_children_ready();
await this._call_lifecycle('on_ready');
this.trigger('ready');
this._log_lifecycle('reload', 'complete (no on_load)');
return;
}
// STEP 1: Cache check (if args changed)
let rendered_from_cache = false;
let data_before_load = null;
@@ -3375,72 +3453,16 @@ class Jqhtml_Component {
}
// Capture data state before on_load for comparison
data_before_load = JSON.stringify(this.data);
// STEP 2: Call on_load() to fetch fresh data
// Unfreeze this.data so we can restore snapshot and on_load() can modify it
this.__data_frozen = false;
// Restore this.data to initial state from snapshot (if not first load)
if (this.__initial_data_snapshot) {
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
}
try {
await this._call_lifecycle('on_load');
}
finally {
// Freeze this.data after on_load() completes
this.__data_frozen = true;
}
// DATA MODE: Normalize this.data through serialize/deserialize round-trip
// (Same as in _load() - ensures hot/cold cache parity)
const reload_cache_mode = Jqhtml_Local_Storage.get_cache_mode();
if (reload_cache_mode === 'data') {
this.__data_frozen = false;
const normalized = Jqhtml_Local_Storage.normalize_for_cache(this.data);
this.data = normalized;
this.__data_frozen = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) normalized this.data after on_load() in reload()`, { data: this.data });
}
}
// Check if data changed during on_load() - if so, update cache (but not if empty)
// STEP 2: Call on_load() on detached proxy
// This uses the same detached execution as _load() - on_load runs isolated
// and result is applied via sequential queue
const result_data = await this._execute_on_load_detached(false); // false = don't use Load_Coordinator
// Apply result via sequential queue
// This handles normalization, caching, and setting _is_dynamic
await this._apply_load_result(result_data, data_before_load);
// Re-read data state after apply (for render decision below)
const data_after_load = JSON.stringify(this.data);
const data_changed = data_after_load !== data_before_load;
if (data_changed && data_after_load !== '{}') {
// Check if component implements cache_id() for custom cache key
let cache_key = null;
if (typeof this.cache_id === 'function') {
try {
const custom_cache_id = this.cache_id();
cache_key = `${this.component_name()}::${String(custom_cache_id)}`;
}
catch (error) {
// cache_id() threw error - disable caching
cache_key = null;
}
}
else {
// Use standard args-based cache key generation
const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
cache_key = result.key;
}
// Write to cache based on mode
if (cache_key !== null) {
this._cache_key = cache_key;
if (reload_cache_mode === 'html') {
// HTML cache mode - flag to cache HTML after children ready in _ready()
this._should_cache_html_after_ready = true;
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache html] Component ${this._cid} (${this.component_name()}) will cache HTML after ready in reload()`, { cache_key });
}
}
else {
// Data cache mode (default) - write this.data to cache
Jqhtml_Local_Storage.set(cache_key, this.data);
if (window.jqhtml?.debug?.verbose) {
console.log(`[Cache data] Component ${this._cid} (${this.component_name()}) cached data after on_load() in reload()`, { cache_key, data: this.data });
}
}
}
}
// STEP 3: Conditionally re-render (with refresh() support)
// Read force_refresh from state machine (default true)
const force_refresh = this.next_reload_force_refresh !== null ? this.next_reload_force_refresh : true;
@@ -3574,6 +3596,11 @@ class Jqhtml_Component {
this._used_cached_html = false;
return true;
}
// OPTIMIZATION: If no custom on_load(), data cannot change, skip serialization
// Data is frozen after on_create() and only unfrozen during on_load()
if (!this.__has_custom_on_load) {
return false;
}
// Compare current data state with data state before initial render
const currentDataState = JSON.stringify(this.data);
const dataChanged = this._data_before_render !== currentDataState;
@@ -4818,7 +4845,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.3.20';
const version = '2.3.21';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
node_modules/@jqhtml/core/dist/jqhtml-debug.esm.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/jqhtml-debug.esm.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/jquery-plugin.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/jquery-plugin.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/load-coordinator.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/load-coordinator.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/local-storage.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/local-storage.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/template-renderer.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/template-renderer.d.ts.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/core/package.json generated vendored Executable file → Normal file
View File

@@ -1,6 +1,6 @@
{
"name": "@jqhtml/core",
"version": "2.3.20",
"version": "2.3.21",
"description": "Core runtime library for JQHTML",
"type": "module",
"main": "./dist/index.js",

0
node_modules/@jqhtml/parser/dist/ast.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/ast.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/ast.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/ast.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/codegen.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/codegen.d.ts.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/parser/dist/codegen.js generated vendored Executable file → Normal file
View File

@@ -1377,7 +1377,7 @@ export class CodeGenerator {
for (const [name, component] of this.components) {
code += `// Component: ${name}\n`;
code += `jqhtml_components.set('${name}', {\n`;
code += ` _jqhtml_version: '2.3.20',\n`; // Version will be replaced during build
code += ` _jqhtml_version: '2.3.21',\n`; // Version will be replaced during build
code += ` name: '${name}',\n`;
code += ` tag: '${component.tagName}',\n`;
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;

0
node_modules/@jqhtml/parser/dist/codegen.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/compiler.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/compiler.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/compiler.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/compiler.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/errors.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/errors.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/errors.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/errors.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/index.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/index.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/index.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/index.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/integration.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/integration.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/integration.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/integration.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/lexer.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/lexer.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/lexer.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/lexer.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/parser.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/parser.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/parser.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/parser.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/runtime.d.ts generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/runtime.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/runtime.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/parser/dist/runtime.js.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/parser/package.json generated vendored Executable file → Normal file
View File

@@ -1,6 +1,6 @@
{
"name": "@jqhtml/parser",
"version": "2.3.20",
"version": "2.3.21",
"description": "JQHTML template parser - converts templates to JavaScript",
"type": "module",
"main": "dist/index.js",

View File

@@ -1 +1 @@
2.3.20
2.3.21

0
node_modules/@jqhtml/vscode-extension/blade-language-configuration.json generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_component_provider.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_component_provider.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_language_config.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_language_config.js.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_spacer.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_spacer.js.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/vscode-extension/package.json generated vendored Executable file → Normal file
View File

@@ -2,7 +2,7 @@
"name": "@jqhtml/vscode-extension",
"displayName": "JQHTML",
"description": "Syntax highlighting and language support for JQHTML template files",
"version": "2.3.20",
"version": "2.3.21",
"publisher": "jqhtml",
"license": "MIT",
"publishConfig": {

0
node_modules/@jqhtml/vscode-extension/syntaxes/blade-jqhtml.tmLanguage.json generated vendored Executable file → Normal file
View File