=== Turbo SPA for Elementor ===
Contributors: devmohamedamjad1
Tags: elementor, spa, navigation, performance, cache
Requires at least: 6.2
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.0.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

SPA-style navigation for Elementor sites. Only the main content changes; header and footer stay in place.

== Description ==

Turbo SPA makes an Elementor site navigate like a single-page app and serves stored copies of pages to anonymous visitors, so pages open without reloading the browser and without rendering WordPress again.

Automatic setup:

* After activation the plugin fetches a sample of the site's own pages on the server, finds the header, footer and content container, validates them like the dashboard does and, when every page passes, switches SPA navigation (for everyone) and static output on.
* Themes without a single content container (header, content and footer side by side) use "everything between header and footer" mode.
* Runs again after a theme switch; "Run automatic setup" on the SPA Navigation page runs it on demand. A site that is already set up keeps its configuration.

What it does:

* Intercepts internal links and swaps only the main content; header, footer, menus and overlays stay as the same DOM nodes. Current-menu highlighting is copied from the new page.
* Updates title, URL, history (Back/Forward), meta description, canonical, Open Graph and JSON-LD tags, body classes and the admin bar.
* Adds the stylesheets the next page needs before showing it, and downloads the scripts it needs in parallel; they still run one by one in page order.
* Prefetches a page when the pointer rests on its link and the main menu pages while the browser is idle.
* Re-initializes widgets on the new content once per visit: Elementor (classic handlers and v4 atomic elements), Essential Addons, Qi Addons, JNews blocks (load more, next/prev, tabs, popups, comments), WPForms and Contact Form 7.
* Cleans up the page being left: Swiper instances, jQuery data and events, window listeners Elementor added for it.
* Restores scroll position on Back/Forward and scrolls to #fragments; fast repeated clicks are handled.
* Falls back to a normal page load whenever a page is excluded, uses another language or header, times out or fails.
* Loading bar at the top of the page, with its color set on the SPA Navigation page (picker or hex code).

Static output (Turbo SPA → Static Compiler and Cache Manager):

* Stores the real output of pages and posts as an anonymous visitor sees it, built through a loopback request or on the first anonymous visit.
* Serves stored copies to anonymous visitors before themes and Elementor load, or before WordPress loads at all through the advanced-cache.php drop-in when no other cache plugin owns it. Logged-in users, visitors with comment or cart cookies, POST requests and URLs with query strings (other than tracking parameters) always get a live render.
* Clears only what changed: a saved post clears itself plus the front and blog pages; templates, global styles, menus, theme, plugin changes and settings clear everything. Cleared pages are rebuilt by a WP-Cron queue.
* Lifetime of at most 12 hours, so nonces inside a copy stay valid; optional skip of pages with nonces.
* Atomic writes, unguessable file names, .htaccess protection, size limit.

Optional: delay third-party scripts (payments, captchas…) until the first interaction (Settings page, off by default).

Performance (Turbo SPA → Performance): server benchmarks per page (live render versus static copy) with history, and optional anonymous navigation statistics (daily counters only; no URL, IP address or cookie is stored; off by default).

Pages keep working without JavaScript: links are ordinary links.

== Installation ==

1. Upload the ZIP from Plugins → Add New → Upload Plugin and activate it.
2. Wait a minute: the plugin checks the site's pages and switches itself on. The Plugins screen shows the result.
3. If it could not finish (for example the server cannot load its own pages), open Turbo SPA → SPA Navigation, click "Detect automatically", then "Run validation", switch on "Enable SPA Navigation" and save.

== Developer notes ==

Events on document: etspa:before-navigation (cancelable), etspa:before-content-replacement (cancelable),
etspa:after-content-replacement, etspa:after-navigation, etspa:navigation-error, etspa:before-fallback.

Add data-no-spa to a link or a container to keep it out of SPA navigation.

Re-initialize your own scripts after navigation:

    document.addEventListener('etspa:after-content-replacement', function (e) {
        initMyWidgets(e.detail.container);
    });

Or register an adapter:

    ETSPA_LIFECYCLE.register({ name: 'my-plugin', mount: function (container) {}, unmount: function (container) {} });

PHP filters: etspa_router_config, etspa_request_eligibility, etspa_default_excluded_paths,
etspa_default_excluded_selectors, etspa_excluded_file_extensions, etspa_safe_script_handles
(script handles that only define a library and may be loaded late in conservative mode), etspa_delay_scripts.

Each etspa:after-navigation event carries detail.assets: the stylesheets and scripts that navigation added.

Page view analytics: Google Analytics 4 counts history changes as page views when "Page changes based on
browser history events" is on in Enhanced measurement (the default). Other tools can listen to etspa:after-navigation.

== Frequently Asked Questions ==

= I edited theme files and visitors still see the old page =

Direct file edits (FTP, theme file editor, deployments) do not trigger WordPress hooks. Click "Clear all cache" in Turbo SPA → Cache Manager afterwards.

= A widget is empty after navigating to its page =

Its plugin starts its widgets only on a full page load. Report the page: an adapter re-initializes that plugin's widgets after each navigation (as for Essential Addons, Qi Addons and JNews).

= How do I check whether a page came from the cache? =

Look at the X-ETSPA-Cache response header: HIT (stored copy), MISS (rendered and stored now) or BYPASS with the reason.

= Does the plugin collect data about visitors? =

Only if you switch on navigation statistics (Performance page). Browsers then send daily counters (number of navigations, fallbacks and their reason, widget errors, total navigation time). No URL, IP address, cookie or identifier is stored, and the endpoint accepts at most 120 reports per minute.

== Privacy Policy ==

This plugin does not contact external servers by default.

Optional navigation statistics (off by default): when enabled by an administrator, browsers send anonymous daily counters to this same WordPress site only (navigations, fallbacks, widget errors, total time). No page URL, IP address, cookie, or personal identifier is stored. You can disable the feature at any time on Turbo SPA → Performance.

Static page copies are stored on the site's own server under uploads and are served only to anonymous visitors according to the settings you choose.

== Changelog ==

= 1.0.4 =
* WordPress.org review: Requires Plugins header for Elementor, contributors list, remove redundant load_plugin_textdomain(), tighten output escaping.

= 1.0.3 =
* The current page stays highlighted in any menu after navigation: links are paired by URL and their active classes and aria-current are copied, also for menus that do not use WordPress menu classes.

= 1.0.2 =
* Page caches of other cache plugins are cleared on the first request after an update (during the update itself they cannot be).

= 1.0.1 =
* Headers that change per page (transparent over a hero on one page, solid with a spacer on others) are replaced with the next page's version during navigation and initialized again.
* Page caches of LiteSpeed Cache, WP Rocket, W3 Total Cache, WP Super Cache and Super Page Cache are cleared when the plugin's settings change or the plugin is updated.

= 1.0.0 =
* Automatic setup after activation and theme switches: server-side detection and validation, then SPA navigation for everyone and static output.
* "Everything between header and footer" content mode, for themes without a single content container (JNews and others), with elements that can be kept in place.
* Early serving through the advanced-cache.php drop-in, with Cache-Control: no-cache and 304 responses.
* Adapters for Essential Addons (tabs, accordions and other widgets whose script arrives with the new page), Qi Addons (sliders and widgets) and JNews (blocks with AJAX load more / next-prev, popups, comments).
* Scripts the next page needs download in parallel; inline scripts that declare top-level let/const run again without "already declared" errors; data scripts are recognized in a single pass.
* Loading bar color setting (picker or hex code).
* Optional delay of third-party scripts until the first interaction (off by default).
* Validation results with non-Latin page titles are stored correctly (text is cut by characters, not bytes).

= 1.0.0-rc.1 =
* Phase 4: Performance page with server benchmarks (live render vs static copy) and history; optional anonymous navigation statistics.
* Phase 5: Elementor Pro popup guard (pages with different popups load normally); static copies are only created for requests made through the site's own host (blocks Host / X-Forwarded-Host cache poisoning); rate limit on the statistics endpoint; security review.

= 0.3.0 =
* Phase 3: static compiler and cache. Loopback and on-visit builds, early serving for anonymous visitors, dependency-based invalidation, WP-Cron rebuild queue.
* New dashboard pages: Static Compiler (page table, bulk builds with real progress, preview, exclude) and Cache Manager.
* Per-page "Never store a static copy" checkbox.
* Uninstall also removes the cache folder, the pages table and scheduled jobs.

= 0.2.0 =
* Phase 2: Elementor lifecycle. Verified with tabs, accordion, toggle, nested tabs/accordion, counter, progress, image carousel, alert, entrance animations, in both modes and after repeated visits.
* Cleanup of the old page: Swiper instances, jQuery data, window listeners added by Elementor handlers (they no longer pile up per visit).
* jQuery ready callbacks of late scripts are held until the batch has loaded (fixes Elementor starting before Swiper in balanced mode).
* No double initialization when Elementor itself arrives with the new page.
* Safe libraries load on the fly in conservative mode; widget CSS inside the content is loaded before the swap.
* Asset report in etspa:after-navigation and in the debug console; validation lists pages that need extra scripts.

= 0.1.0 =
* Phase 1: SPA navigation engine, widget lifecycle, exclusions, selector validation, dashboard.
