Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Feb 5, 2026

User description

This PR adds a global safety fallback for HTMX event handlers in the root layout. This prevents production failures caused by browser extensions (like Phantom Wallet) attempting to call undefined HTMX functions during SSE streams.


PR Type

Bug fix


Description

  • Adds global safety fallback for HTMX event handlers in root layout

  • Prevents production failures from browser extensions calling undefined functions

  • Defines stub functions for 11 HTMX events with console warnings

  • Executes initialization script in document head before body rendering


Diagram Walkthrough

flowchart LR
  A["Browser Extension"] -->|"calls undefined HTMX function"| B["Safety Fallback Script"]
  B -->|"checks if function exists"| C["Define Stub Function"]
  C -->|"logs warning"| D["Console Warning"]
Loading

File Walkthrough

Relevant files
Bug fix
layout.tsx
Add HTMX event handler safety fallback script                       

app/layout.tsx

  • Adds section with inline script to root layout
  • Defines 11 HTMX event handler stubs (sseError, sseOpen, swapError,
    etc.)
  • Checks if each handler exists before defining fallback function
  • Logs console warning when fallback handler is invoked
+23/-0   

Summary by CodeRabbit

  • Bug Fixes
    • Improved error handling for event handlers with fallback mechanisms and enhanced console warnings to help identify and diagnose issues.

@vercel
Copy link
Contributor

vercel bot commented Feb 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
qcx Ready Ready Preview, Comment Feb 5, 2026 7:24am

@charliecreates charliecreates bot requested a review from CharlieHelps February 5, 2026 07:22
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Walkthrough

Added a safety bootstrap script to the document head that initializes fallback HTMX event handler functions. For a predefined set of HTMX events, global functions are created if not already defined, each logging a console warning when invoked to prevent runtime errors from undefined handlers.

Changes

Cohort / File(s) Summary
HTMX Event Handler Safety Bootstrap
app/layout.tsx
Added 23 lines containing a bootstrap script that defines fallback functions for HTMX events, creating global handler stubs that log console warnings if invoked, preventing undefined handler errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 With handlers now safe from the void,
HTMX events won't feel annoyed,
Each function stands guard with a gentle alert,
No silent crashes, no code gets hurt! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix: func sseError not found (Extension Conflict)' directly addresses the main purpose of the PR: preventing runtime errors from browser extensions trying to call undefined HTMX event handlers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/sse-error-extension-conflict

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Inline script XSS

Description: The new inline <script> uses dangerouslySetInnerHTML, which bypasses React/Next.js escaping and
can weaken CSP (requires unsafe-inline or nonces); while the current payload is static,
this pattern is a realistic XSS footgun if any dynamic/untrusted content is later
interpolated into the script.
layout.tsx [68-89]

Referred Code
<head>
  <script
    dangerouslySetInnerHTML={{
      __html: `
        (function() {
          const htmxEvents = [
            'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
            'validation:validate', 'validation:failed', 'validation:halted',
            'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
          ];
          htmxEvents.forEach(event => {
            const funcName = 'func ' + event;
            if (typeof window[funcName] === 'undefined') {
              window[funcName] = function() { 
                console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
              };
            }
          });
        })();
      `,
    }}


 ... (clipped 1 lines)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Inline script injection: The use of dangerouslySetInnerHTML to inject an inline <script> should be reviewed
against CSP/XSS requirements even if the injected content is currently static.

Referred Code
<script
  dangerouslySetInnerHTML={{
    __html: `
      (function() {
        const htmxEvents = [
          'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
          'validation:validate', 'validation:failed', 'validation:halted',
          'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
        ];
        htmxEvents.forEach(event => {
          const funcName = 'func ' + event;
          if (typeof window[funcName] === 'undefined') {
            window[funcName] = function() { 
              console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
            };
          }
        });
      })();
    `,
  }}
/>

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Consider a more robust alternative

Instead of patching the issue with global stub functions, consider a more robust
refactor. This would involve using programmatic event listeners for HTMX events
to avoid reliance on the global scope.

Examples:

app/layout.tsx [69-89]
        <script
          dangerouslySetInnerHTML={{
            __html: `
              (function() {
                const htmxEvents = [
                  'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
                  'validation:validate', 'validation:failed', 'validation:halted',
                  'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
                ];
                htmxEvents.forEach(event => {

 ... (clipped 11 lines)

Solution Walkthrough:

Before:

// app/layout.tsx
function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        <script
          dangerouslySetInnerHTML={{
            __html: `
              // Define global stub functions for HTMX events
              // if they don't already exist.
              const htmxEvents = [...];
              htmxEvents.forEach(event => {
                const funcName = 'func ' + event;
                if (typeof window[funcName] === 'undefined') {
                  window[funcName] = function() { /* log warning */ };
                }
              });
            `,
          }}
        />
      </head>
      <body>...</body>
    </html>
  );
}

After:

// app/layout.tsx
function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {/* The global fallback script is removed. */}
      </head>
      <body>...</body>
    </html>
  );
}

// In a relevant component, e.g., using React hooks
useEffect(() => {
  // Event listeners are attached programmatically instead of
  // relying on global functions.
  const handleSseError = (event) => { /* ... */ };
  document.body.addEventListener('htmx:sseError', handleSseError);
  return () => document.body.removeEventListener('htmx:sseError', handleSseError);
}, []);
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies the PR's approach as a patch and proposes a more robust, architectural long-term solution, which is a significant and valid point for improving code quality.

Medium
General
Switch to Next.js Script component

Replace the manual <script> tag inside with the Next.js <Script> component to leverage
framework optimizations.

app/layout.tsx [68-90]

-<head>
-  <script
-    dangerouslySetInnerHTML={{
-      __html: `
-        (function() {
-          /* ... */
-        })();
-      `,
-    }}
-  />
-</head>
+import Script from 'next/script'
+...
+<Script
+  id="htmx-fallback-handlers"
+  strategy="beforeInteractive"
+  dangerouslySetInnerHTML={{
+    __html: `
+      (function() {
+        /* ... */
+      })();
+    `,
+  }}
+/>

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 7

__

Why: This suggestion correctly recommends using the idiomatic Next.js <Script> component, which improves performance and aligns with the framework's best practices for handling external scripts.

Medium
Possible issue
Normalize fallback handler names

Normalize the HTMX event handler names into valid JavaScript identifiers by
replacing spaces and special characters with underscores.

app/layout.tsx [79]

-const funcName = 'func ' + event;
+const funcName = 'func_' + event.replace(/[^A-Za-z0-9_$]/g, '_');
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that creating function names with spaces is unconventional and improves code robustness by creating valid JavaScript identifiers, which is better practice.

Low
  • More

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline global fallback is a pragmatic mitigation, but it’s currently overly broad and always-on via dangerouslySetInnerHTML, which increases the chance of unintended side effects in a root layout. Consider using next/script with an early loading strategy plus explicit browser guards. Additionally, the current console.warn may spam logs during SSE activity; dedupe or restrict warnings (e.g., dev-only) to keep production noise down.

Additional notes (1)
  • Maintainability | app/layout.tsx:73-85
    Using a global naming convention like window['func ' + event] is very broad and may mask genuine configuration issues (or conflict with other tooling) beyond the specific Phantom Wallet scenario. If the problem is limited to a known set of extensions/events, consider narrowing the patch to the minimum necessary surface area (e.g., only sseError/sseOpen) or gating the fallback behind detection of HTMX usage/presence to avoid papering over real errors.
Summary of changes

What changed

  • Added an explicit <head> section to app/layout.tsx.
  • Injected an inline <script> via dangerouslySetInnerHTML that:
    • Defines a list of HTMX-related event names (e.g., sseError, sseOpen, xhr:loadstart).
    • For each event, creates a global window["func " + event] no-op handler if it doesn’t already exist.
    • Emits a console.warn(...) when a missing handler is invoked, acting as a safety fallback against browser extension conflicts.

Comment on lines +68 to +90
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
const htmxEvents = [
'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
'validation:validate', 'validation:failed', 'validation:halted',
'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
];
htmxEvents.forEach(event => {
const funcName = 'func ' + event;
if (typeof window[funcName] === 'undefined') {
window[funcName] = function() {
console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
};
}
});
})();
`,
}}
/>
</head>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script uses dangerouslySetInnerHTML without any scoping/guards and will run on every page load, potentially in environments where window isn’t available (or before extensions/HTMX are ready). While the code references window only at runtime, it’s still worth explicitly guarding for non-browser contexts and avoiding unnecessary work when document/window aren’t present.

Also, because this is a root layout in Next.js, you should be deliberate about when the script runs. If the goal is to prevent early calls from extensions, consider next/script with an appropriate strategy (e.g., beforeInteractive) and a stable id to avoid duplication/rehydration surprises.

Suggestion

Consider switching to next/script and adding explicit guards:

  • Use <Script id="htmx-func-fallback" strategy="beforeInteractive"> to ensure the fallback exists as early as possible.
  • Add if (typeof window === 'undefined') return; inside the IIFE to harden against non-browser execution.

Example:

import Script from 'next/script'

// ...
<head>
  <Script id="htmx-func-fallback" strategy="beforeInteractive">
    {`(function(){
      if (typeof window === 'undefined') return;
      const htmxEvents = [/* ... */];
      for (const event of htmxEvents) {
        const funcName = 'func ' + event;
        if (typeof window[funcName] === 'undefined') {
          window[funcName] = function() {};
        }
      }
    })();`}
  </Script>
</head>

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.

Comment on lines +78 to +84
htmxEvents.forEach(event => {
const funcName = 'func ' + event;
if (typeof window[funcName] === 'undefined') {
window[funcName] = function() {
console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
};
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback function logs a warning every time an extension triggers the missing handler, which can spam the console (and, in some setups, log aggregation) during SSE streams. Since the intent is resilience, not observability, this should ideally warn once per handler (or be gated behind a development check).

Suggestion

Rate-limit or dedupe the warning per handler (or only warn in development). For example:

const warned = new Set();
// ...
window[funcName] = function() {
  if (!warned.has(funcName)) {
    warned.add(funcName);
    console.warn(`HTMX event handler "${funcName}" was called but not defined. Providing safety fallback.`);
  }
};

Alternatively, log only when process.env.NODE_ENV !== 'production'.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@app/layout.tsx`:
- Around line 68-90: Move the inline HTMX fallback script out of app/layout.tsx
into an external static file (e.g., public/htmx-fallback.js) that contains the
IIFE creating the htmxEvents array and the window['func ' + event] safety
handlers, then load that file with Next.js's next/script using
strategy="beforeInteractive" in layout.tsx (or alternatively generate and apply
a CSP nonce and add it to the inline script) so the bootstrap runs when
script-src disallows unsafe-inline; update references to the IIFE/htmxEvents and
the window handler names (the 'func ' + event handlers) to ensure the same
behavior after moving the code.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4c7ca0 and 6b7118f.

📒 Files selected for processing (1)
  • app/layout.tsx
🧰 Additional context used
🪛 ast-grep (0.40.5)
app/layout.tsx

[warning] 69-69: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.3.13)
app/layout.tsx

[error] 70-70: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +68 to +90
<head>
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
const htmxEvents = [
'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
'validation:validate', 'validation:failed', 'validation:halted',
'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
];
htmxEvents.forEach(event => {
const funcName = 'func ' + event;
if (typeof window[funcName] === 'undefined') {
window[funcName] = function() {
console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
};
}
});
})();
`,
}}
/>
</head>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Inline script may be blocked by CSP; prefer external script or nonce.

If script-src disallows unsafe-inline, this script won’t run and the fallback won’t be installed. Consider moving the bootstrap to a static file loaded with next/script (beforeInteractive) or apply a CSP nonce if you use nonces.

🛠️ Proposed refactor (external script)
+import Script from 'next/script'
...
-      <head>
-        <script
-          dangerouslySetInnerHTML={{
-            __html: `
-              (function() {
-                const htmxEvents = [
-                  'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
-                  'validation:validate', 'validation:failed', 'validation:halted',
-                  'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
-                ];
-                htmxEvents.forEach(event => {
-                  const funcName = 'func ' + event;
-                  if (typeof window[funcName] === 'undefined') {
-                    window[funcName] = function() { 
-                      console.warn('HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.');
-                    };
-                  }
-                });
-              })();
-            `,
-          }}
-        />
-      </head>
+      <head>
+        <Script src="/htmx-fallback.js" strategy="beforeInteractive" />
+      </head>

Then add public/htmx-fallback.js:

(function () {
  const htmxEvents = [
    'sseError', 'sseOpen', 'swapError', 'targetError', 'timeout',
    'validation:validate', 'validation:failed', 'validation:halted',
    'xhr:abort', 'xhr:loadend', 'xhr:loadstart'
  ];
  htmxEvents.forEach(event => {
    const funcName = 'func ' + event;
    if (typeof window[funcName] === 'undefined') {
      window[funcName] = function () {
        console.warn(
          'HTMX event handler "' + funcName + '" was called but not defined. Providing safety fallback.'
        );
      };
    }
  });
})();
🧰 Tools
🪛 ast-grep (0.40.5)

[warning] 69-69: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.3.13)

[error] 70-70: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🤖 Prompt for AI Agents
In `@app/layout.tsx` around lines 68 - 90, Move the inline HTMX fallback script
out of app/layout.tsx into an external static file (e.g.,
public/htmx-fallback.js) that contains the IIFE creating the htmxEvents array
and the window['func ' + event] safety handlers, then load that file with
Next.js's next/script using strategy="beforeInteractive" in layout.tsx (or
alternatively generate and apply a CSP nonce and add it to the inline script) so
the bootstrap runs when script-src disallows unsafe-inline; update references to
the IIFE/htmxEvents and the window handler names (the 'func ' + event handlers)
to ensure the same behavior after moving the code.

@ngoiyaeric ngoiyaeric merged commit 5c25e9f into main Feb 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant