From a5ab6dd0aab2d68779e146f6de42f303ae70c3c0 Mon Sep 17 00:00:00 2001 From: Julio Lopez Date: Thu, 29 Jan 2026 13:46:14 +0100 Subject: [PATCH 1/4] Enhance PageObserver with detailed logging and handling for script loading states - Added console logs to track the start, state changes, and interactions within the PageObserver. - Implemented handling for scripts loaded with the defer attribute, ensuring that DOMContentLoaded is awaited before firing turbo:load. --- src/observers/page_observer.js | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/observers/page_observer.js b/src/observers/page_observer.js index 934d45c45..3b7790b50 100644 --- a/src/observers/page_observer.js +++ b/src/observers/page_observer.js @@ -15,12 +15,30 @@ export class PageObserver { start() { if (!this.started) { + console.log(`[PageObserver] start() called - stage: ${this.getStageLabel(this.stage)}, readyState: ${document.readyState}, timestamp: ${Date.now()}`) + if (this.stage == PageStage.initial) { this.stage = PageStage.loading } document.addEventListener("readystatechange", this.interpretReadyState, false) addEventListener("pagehide", this.pageWillUnload, false) this.started = true + + // Handle case where script loads with defer attribute + // Deferred scripts execute when readyState is "interactive" (after HTML parsing, before DOMContentLoaded) + // We need to wait for DOMContentLoaded to ensure all deferred scripts have executed before firing turbo:load + console.log(`[PageObserver] checking initial readyState: ${document.readyState}, timestamp: ${Date.now()}`) + + if (document.readyState === "interactive") { + // Script loaded with defer - wait for DOMContentLoaded to ensure all deferred scripts are ready + console.log(`[PageObserver] readyState is 'interactive', waiting for DOMContentLoaded before firing turbo:load`) + document.addEventListener("DOMContentLoaded", () => { + console.log(`[PageObserver] DOMContentLoaded fired, now checking state - timestamp: ${Date.now()}`) + this.interpretReadyState() + }, { once: true }) + } + // Note: If readyState is "loading", listeners will catch state changes normally + // Note: If readyState is "complete", script loaded async after page load - don't fire turbo:load (intentional) } } @@ -34,6 +52,8 @@ export class PageObserver { interpretReadyState = () => { const { readyState } = this + console.log(`[PageObserver] interpretReadyState - readyState: ${readyState}, stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) + if (readyState == "interactive") { this.pageIsInteractive() } else if (readyState == "complete") { @@ -42,16 +62,24 @@ export class PageObserver { } pageIsInteractive() { + console.log(`[PageObserver] pageIsInteractive - stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) + if (this.stage == PageStage.loading) { this.stage = PageStage.interactive + console.log(`[PageObserver] 🎯 FIRING turbo:load (pageBecameInteractive) - timestamp: ${Date.now()}`) this.delegate.pageBecameInteractive() + } else { + console.log(`[PageObserver] ⏭️ SKIPPING pageBecameInteractive (stage already ${this.getStageLabel(this.stage)})`) } } pageIsComplete() { + console.log(`[PageObserver] pageIsComplete - stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) + this.pageIsInteractive() if (this.stage == PageStage.interactive) { this.stage = PageStage.complete + console.log(`[PageObserver] ✅ Page fully loaded (pageLoaded) - timestamp: ${Date.now()}`) this.delegate.pageLoaded() } } @@ -63,4 +91,14 @@ export class PageObserver { get readyState() { return document.readyState } + + getStageLabel(stage) { + const labels = { + [PageStage.initial]: 'initial', + [PageStage.loading]: 'loading', + [PageStage.interactive]: 'interactive', + [PageStage.complete]: 'complete' + } + return labels[stage] || 'unknown' + } } From 398533b58b5afb58e1c382c259f92fc6cdb66034 Mon Sep 17 00:00:00 2001 From: Julio Lopez Date: Thu, 29 Jan 2026 13:55:07 +0100 Subject: [PATCH 2/4] add build on prepare to the package --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4eafd2e90..947aba27b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "test:unit": "NODE_OPTIONS=--inspect web-test-runner", "test:unit:win": "SET NODE_OPTIONS=--inspect & web-test-runner", "release": "yarn build && yarn publish", - "lint": "eslint . --ext .js" + "lint": "eslint . --ext .js", + "prepare": "yarn build" }, "engines": { "node": ">= 18" From 877c93af9c1e31dc107479a71b92449583ad357a Mon Sep 17 00:00:00 2001 From: Julio Lopez Date: Fri, 30 Jan 2026 11:01:35 +0100 Subject: [PATCH 3/4] remove debugging logs and comments --- src/observers/page_observer.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/observers/page_observer.js b/src/observers/page_observer.js index 3b7790b50..c0a69e99f 100644 --- a/src/observers/page_observer.js +++ b/src/observers/page_observer.js @@ -15,30 +15,17 @@ export class PageObserver { start() { if (!this.started) { - console.log(`[PageObserver] start() called - stage: ${this.getStageLabel(this.stage)}, readyState: ${document.readyState}, timestamp: ${Date.now()}`) - if (this.stage == PageStage.initial) { this.stage = PageStage.loading } document.addEventListener("readystatechange", this.interpretReadyState, false) addEventListener("pagehide", this.pageWillUnload, false) this.started = true - - // Handle case where script loads with defer attribute - // Deferred scripts execute when readyState is "interactive" (after HTML parsing, before DOMContentLoaded) - // We need to wait for DOMContentLoaded to ensure all deferred scripts have executed before firing turbo:load - console.log(`[PageObserver] checking initial readyState: ${document.readyState}, timestamp: ${Date.now()}`) - if (document.readyState === "interactive") { - // Script loaded with defer - wait for DOMContentLoaded to ensure all deferred scripts are ready - console.log(`[PageObserver] readyState is 'interactive', waiting for DOMContentLoaded before firing turbo:load`) document.addEventListener("DOMContentLoaded", () => { - console.log(`[PageObserver] DOMContentLoaded fired, now checking state - timestamp: ${Date.now()}`) this.interpretReadyState() }, { once: true }) } - // Note: If readyState is "loading", listeners will catch state changes normally - // Note: If readyState is "complete", script loaded async after page load - don't fire turbo:load (intentional) } } @@ -52,7 +39,6 @@ export class PageObserver { interpretReadyState = () => { const { readyState } = this - console.log(`[PageObserver] interpretReadyState - readyState: ${readyState}, stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) if (readyState == "interactive") { this.pageIsInteractive() @@ -62,24 +48,16 @@ export class PageObserver { } pageIsInteractive() { - console.log(`[PageObserver] pageIsInteractive - stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) - if (this.stage == PageStage.loading) { this.stage = PageStage.interactive - console.log(`[PageObserver] 🎯 FIRING turbo:load (pageBecameInteractive) - timestamp: ${Date.now()}`) this.delegate.pageBecameInteractive() - } else { - console.log(`[PageObserver] ⏭️ SKIPPING pageBecameInteractive (stage already ${this.getStageLabel(this.stage)})`) } } pageIsComplete() { - console.log(`[PageObserver] pageIsComplete - stage: ${this.getStageLabel(this.stage)}, timestamp: ${Date.now()}`) - this.pageIsInteractive() if (this.stage == PageStage.interactive) { this.stage = PageStage.complete - console.log(`[PageObserver] ✅ Page fully loaded (pageLoaded) - timestamp: ${Date.now()}`) this.delegate.pageLoaded() } } From d0eb2d17bc50b38689ac2641cd6c0893713315af Mon Sep 17 00:00:00 2001 From: Julio Lopez Date: Fri, 30 Jan 2026 13:26:13 +0100 Subject: [PATCH 4/4] remove unused helper --- src/observers/page_observer.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/observers/page_observer.js b/src/observers/page_observer.js index c0a69e99f..27c8fc693 100644 --- a/src/observers/page_observer.js +++ b/src/observers/page_observer.js @@ -39,7 +39,6 @@ export class PageObserver { interpretReadyState = () => { const { readyState } = this - if (readyState == "interactive") { this.pageIsInteractive() } else if (readyState == "complete") { @@ -69,14 +68,4 @@ export class PageObserver { get readyState() { return document.readyState } - - getStageLabel(stage) { - const labels = { - [PageStage.initial]: 'initial', - [PageStage.loading]: 'loading', - [PageStage.interactive]: 'interactive', - [PageStage.complete]: 'complete' - } - return labels[stage] || 'unknown' - } }