From bded711d1c37921908993f7495bac77da5373c0d Mon Sep 17 00:00:00 2001 From: root Date: Mon, 29 Dec 2025 05:49:17 +0000 Subject: [PATCH] Convert Spa.action property to Spa.action() method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/RSpade/Core/SPA/Spa.js | 34 ++++- app/RSpade/man/spa.txt | 10 +- .../form_value_persistence_12_29.txt | 12 +- .../spa_action_method_12_29.txt | 116 ++++++++++++++++++ docs/CLAUDE.dist.md | 4 +- 5 files changed, 161 insertions(+), 15 deletions(-) create mode 100755 app/RSpade/upstream_changes/spa_action_method_12_29.txt diff --git a/app/RSpade/Core/SPA/Spa.js b/app/RSpade/Core/SPA/Spa.js index 5e1e64aa5..a48a62900 100755 --- a/app/RSpade/Core/SPA/Spa.js +++ b/app/RSpade/Core/SPA/Spa.js @@ -19,8 +19,8 @@ class Spa { // Current layout instance static layout = null; - // Current action instance - static action = null; + // Current action instance (use Spa.action() to access) + static _action = null; // Current route instance static route = null; @@ -49,6 +49,29 @@ class Spa { // Grace period in milliseconds for suppressing errors after navigation static NAVIGATION_GRACE_PERIOD_MS = 10000; + /** + * Get the current action component instance + * + * Returns the cached action if available, otherwise finds it from the DOM. + * This method should be used instead of direct property access to ensure + * the action is always found even if the cache was cleared. + * + * @returns {Spa_Action|null} The current action instance, or null if none + */ + static action() { + if (Spa._action) { + return Spa._action; + } + + const $spa_action = $('.Spa_Action').first(); + if (!$spa_action.exists()) { + return null; + } + + Spa._action = $spa_action.component(); + return Spa._action; + } + /** * Check if we're within the navigation grace period * @@ -543,6 +566,9 @@ class Spa { Spa.is_dispatching = true; + // Clear cached action - will be set when new action is created + Spa._action = null; + // Record navigation timestamp for error suppression grace period // Errors from previous page's pending requests should be ignored for 10 seconds Spa._navigation_timestamp = Date.now(); @@ -911,7 +937,7 @@ class Spa { if (is_last) { // This is the action - set reference but don't wait - Spa.action = component; + Spa._action = component; } else { // This is a layout // Wait for render to complete (not full ready - we don't need child data to load) @@ -932,7 +958,7 @@ class Spa { for (const layout of layouts_for_on_action) { // Set action reference before calling on_action so layouts can access it - layout.action = Spa.action; + layout.action = Spa.action(); if (layout.on_action) { layout.on_action(url, action_name, args); } diff --git a/app/RSpade/man/spa.txt b/app/RSpade/man/spa.txt index d99f2a941..3782a2979 100755 --- a/app/RSpade/man/spa.txt +++ b/app/RSpade/man/spa.txt @@ -419,7 +419,7 @@ SUBLAYOUTS References: Spa.layout Always the top-level layout - Spa.action Always the bottom-level action (not a layout) + Spa.action() Always the bottom-level action (not a layout) To access intermediate layouts, use DOM traversal or layout hooks. @@ -516,11 +516,11 @@ NAVIGATION Accessing Active Components: Spa.layout Reference to current layout component instance - Spa.action Reference to current action component instance + Spa.action() Reference to current action component instance Example: // From anywhere in SPA context - Spa.action.reload(); // Reload current action + Spa.action().reload(); // Reload current action Spa.layout.update_nav(); // Call layout method Loader Title Hint: @@ -821,7 +821,7 @@ COMMON PATTERNS Reloading Current Action: // Refresh current action data - Spa.action.reload(); + Spa.action().reload(); Form Submission with Redirect: async on_submit() { @@ -880,7 +880,7 @@ DETACHED ACTION LOADING }); What It Does NOT Affect: - - Spa.action (current live action remains unchanged) + - Spa.action() (current live action remains unchanged) - Spa.layout (current live layout remains unchanged) - Spa.route / Spa.params (current route state unchanged) - Browser history diff --git a/app/RSpade/upstream_changes/form_value_persistence_12_29.txt b/app/RSpade/upstream_changes/form_value_persistence_12_29.txt index 4b3827e95..83ba08009 100755 --- a/app/RSpade/upstream_changes/form_value_persistence_12_29.txt +++ b/app/RSpade/upstream_changes/form_value_persistence_12_29.txt @@ -33,9 +33,13 @@ CHANGES REQUIRED // the cache revalidates and the form re-renders with fresh data. // This system caches user changes and re-applies them after re-render. + // Get parent component (closest .Component that isn't this one) + const $parent = this.$.closest('.Component').not(this.$); + const parent_component = $parent.exists() ? $parent.component() : null; + // Determine cache storage location and key - const cache_location = this.parent() || this; - const cache_key = this.parent() ? `__formvals_${this._cid}` : '__this_formvals'; + const cache_location = parent_component || this; + const cache_key = parent_component ? `__formvals_${this._cid}` : '__this_formvals'; // Initialize cache if it doesn't exist if (!cache_location[cache_key]) { @@ -118,10 +122,10 @@ VERIFICATION 5. In browser console, you can inspect the cache: // If form has a parent: - Spa.action.__formvals_form + Spa.action().__formvals_form // Or check what's stored: - console.log(Object.keys(Spa.action).filter(k => k.startsWith('__formvals'))); + console.log(Object.keys(Spa.action()).filter(k => k.startsWith('__formvals'))); REFERENCE diff --git a/app/RSpade/upstream_changes/spa_action_method_12_29.txt b/app/RSpade/upstream_changes/spa_action_method_12_29.txt new file mode 100755 index 000000000..a7f2695e8 --- /dev/null +++ b/app/RSpade/upstream_changes/spa_action_method_12_29.txt @@ -0,0 +1,116 @@ +SPA ACTION METHOD - MIGRATION GUIDE +Date: 2025-12-29 + +SUMMARY + + The Spa.action property has been converted to a method: Spa.action(). + This change ensures the current action is always accessible, even when + the cached reference is cleared during navigation. The method will find + the action from the DOM if the cache is empty. + + All existing code that accesses Spa.action must be updated to Spa.action(). + +AFFECTED FILES + + Any JavaScript file that accesses Spa.action: + - SPA action classes + - Layout classes + - Utility code that interacts with the current action + +CHANGES REQUIRED + + 1. Update All Spa.action References to Spa.action() + + BEFORE: + Spa.action.reload(); + const title = Spa.action.get_title(); + if (Spa.action) { ... } + + AFTER: + Spa.action().reload(); + const title = Spa.action().get_title(); + if (Spa.action()) { ... } + + 2. Search and Replace Pattern + + Find all occurrences in your codebase: + grep -r "Spa\.action[^(]" --include="*.js" rsx/ + + Replace Spa.action with Spa.action() (note: don't match Spa.action() itself) + + 3. Common Usage Patterns + + Reloading the current action: + BEFORE: Spa.action.reload(); + AFTER: Spa.action().reload(); + + Accessing action data: + BEFORE: const data = Spa.action.data; + AFTER: const data = Spa.action().data; + + Checking if action exists: + BEFORE: if (Spa.action) { ... } + AFTER: if (Spa.action()) { ... } + + Getting action args: + BEFORE: const id = Spa.action.args.id; + AFTER: const id = Spa.action().args.id; + +HOW IT WORKS + + The Spa.action() method: + 1. Returns the cached _action if available + 2. If cache is empty, finds $('.Spa_Action').first() + 3. Gets the component from that element + 4. Caches and returns it + 5. Returns null if no action exists + + The cache is cleared at the start of each Spa.dispatch() call, ensuring + the old action reference doesn't persist during navigation. + + Code Example: + static action() { + if (Spa._action) { + return Spa._action; + } + + const $spa_action = $('.Spa_Action').first(); + if (!$spa_action.exists()) { + return null; + } + + Spa._action = $spa_action.component(); + return Spa._action; + } + +WHY THIS CHANGE + + The property-based approach (Spa.action) had timing issues: + - During navigation, Spa.action could be null or stale + - Forms using stale-while-revalidate caching need reliable action access + - The method approach ensures the action is always found if it exists + + The method-based approach: + - Always returns the current action if one exists in the DOM + - Falls back to DOM lookup when cache is cleared + - Provides consistent behavior during navigation transitions + +CONFIGURATION + + No configuration required. The change is automatic. + +VERIFICATION + + 1. Search your codebase for Spa.action usage: + grep -r "Spa\.action[^(]" --include="*.js" rsx/ + + 2. Update all occurrences to Spa.action() + + 3. Test SPA navigation works correctly + + 4. Test that Spa.action().reload() works from console + +REFERENCE + + php artisan rsx:man spa + system/app/RSpade/Core/SPA/Spa.js diff --git a/docs/CLAUDE.dist.md b/docs/CLAUDE.dist.md index 1fbf50c4b..2345b8fcc 100644 --- a/docs/CLAUDE.dist.md +++ b/docs/CLAUDE.dist.md @@ -332,8 +332,8 @@ Rsx::Route('Contacts_View_Action', 123) // /contacts/123 ```javascript Spa.dispatch('/contacts/123'); // Programmatic navigation -Spa.layout // Current layout instance -Spa.action // Current action instance +Spa.layout // Current layout instance +Spa.action() // Current action instance ``` ### URL Parameters