Repository: adobecom/milo
Pull request #3798: [MWPW-167072][LANA] Add parameter for severity
Author: login: thedoc31
Labels: name: Ready for Stage
Created: 2025-03-10T16:48:45Z | Merged: 2025-04-28T16:10:20Z

<!-- Before submitting, please review all open PRs. -->

Per #3790, add LANA support for event severity. This will allow engineers to specify whether an issue is `debug/info/warn/error/critical` in priority and will allow the SRE team to take action accordingly. For instance, `critical` severity alerts should automatically open a CSO, and `debug` can be used for additional logging info when triaging issues.

Resolves: [MWPW-167072](https://jira.corp.adobe.com/browse/MWPW-167072)

**Test URLs:**
- Before: https://main--milo--adobecom.aem.page/?martech=off
- After: https://MWPW-167072--milo--adobecom.aem.page/?martech=off



Diff:
diff --git a/libs/utils/lana.js b/libs/utils/lana.js
index 8622950eaed..09a498be5ed 100644
--- a/libs/utils/lana.js
+++ b/libs/utils/lana.js
@@ -15,6 +15,8 @@
     isProdDomain: false,
   };
 
+  const VALID_SEVERITIES = new Set(['d', 'debug', 'i', 'info', 'w', 'warn', 'e', 'error', 'c', 'critical']);
+
   const w = window;
 
   function isProd() {
@@ -52,6 +54,14 @@
     }, {});
   }
 
+  function hasDebugParam() {
+    return w.location.search.toLowerCase().indexOf('lanadebug') !== -1;
+  }
+
+  function isLocalhost() {
+    return w.location.host.toLowerCase().indexOf('localhost') !== -1;
+  }
+
   function log(msg, options) {
     msg = msg && msg.stack ? msg.stack : (msg || '');
     if (msg.length > MSG_LIMIT) {
@@ -64,6 +74,20 @@
       return;
     }
 
+    let severity;
+    if (options && options.severity !== undefined) {
+      if (VALID_SEVERITIES.has(options.severity)) {
+        severity = options.severity;
+      } else {
+        const isDebugMode = hasDebugParam() || w.lana.debug;
+        const defaultSeverity = isDebugMode ? 'd' : 'i';
+        console.warn(`LANA: Invalid severity '${options.severity}'. Defaulting to '${defaultSeverity}'.`);
+        severity = defaultSeverity;
+      }
+    } else if (w.lana.debug) {
+      severity = 'd';
+    }
+
     const sampleRateParam = parseInt(new URL(window.location).searchParams.get('lana-sample'), 10);
     const sampleRate = sampleRateParam || (o.errorType === 'i' ? o.implicitSampleRate : o.sampleRate);
 
@@ -79,6 +103,10 @@
       `t=${encodeURI(o.errorType)}`,
     ];
 
+    if (severity) {
+      queryParams.push(`r=${encodeURI(severity)}`);
+    }
+
     if (o.tags) {
       queryParams.push(`tags=${encodeURI(o.tags)}`);
     }
@@ -104,14 +132,6 @@
     log(e.reason || e.error || e.message, { errorType: 'i' });
   }
 
-  function hasDebugParam() {
-    return w.location.search.toLowerCase().indexOf('lanadebug') !== -1;
-  }
-
-  function isLocalhost() {
-    return w.location.host.toLowerCase().indexOf('localhost') !== -1;
-  }
-
   w.lana = {
     debug: false,
     log,
diff --git a/test/utils/lana.test.js b/test/utils/lana.test.js
index b01db58e31b..59bd532210b 100644
--- a/test/utils/lana.test.js
+++ b/test/utils/lana.test.js
@@ -97,6 +97,11 @@ describe('LANA', () => {
     window.lana.debug = true;
     window.lana.log('Test debug log message', { clientId: 'debugClientId' });
     const serverResponse = 'client=debugClientId,type=e,sample=1,user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36,referer=undefined,ip=23.56.175.228,message=Test debug log message';
+
+    // Check the URL to make sure it includes r=d and the debug flag
+    expect(xhrRequests[0].url).to.contain('r=d');
+    expect(xhrRequests[0].url).to.contain('&d');
+
     xhrRequests[0].respond(200, { 'Content-Type': 'text/html' }, serverResponse);
 
     setTimeout(() => {
@@ -224,4 +229,82 @@ describe('LANA', () => {
 
     window.history.pushState({ path: originalUrl }, '', originalUrl);
   });
+
+  it('uses severity if defined in options', () => {
+    window.lana.log('Testing severity', { severity: 'w' });
+    expect(xhrRequests.length).to.equal(1);
+    expect(xhrRequests[0].method).to.equal('GET');
+    expect(xhrRequests[0].url).to.equal(
+      'https://www.stage.adobe.com/lana/ll?m=Testing%20severity&c=testClientId&s=100&t=e&r=w',
+    );
+  });
+
+  it('uses debug severity when in debug mode', () => {
+    window.lana.debug = true;
+    window.lana.log('Debug mode test');
+    expect(xhrRequests.length).to.equal(1);
+    expect(xhrRequests[0].method).to.equal('GET');
+    expect(xhrRequests[0].url).to.equal(
+      'https://www.stage.adobe.com/lana/ll?m=Debug%20mode%20test&c=testClientId&s=100&t=e&r=d&d',
+    );
+  });
+
+  it('explicit severity takes precedence over debug mode default', () => {
+    window.lana.debug = true;
+    window.lana.log('Explicit severity test', { severity: 'w' });
+    expect(xhrRequests.length).to.equal(1);
+    expect(xhrRequests[0].method).to.equal('GET');
+    expect(xhrRequests[0].url).to.equal(
+      'https://www.stage.adobe.com/lana/ll?m=Explicit%20severity%20test&c=testClientId&s=100&t=e&r=w&d',
+    );
+
+    // Also test with invalid severity to verify debug mode is used for fallback
+    window.lana.log('Invalid severity test', { severity: 'invalid' });
+    expect(xhrRequests.length).to.equal(2);
+    expect(xhrRequests[1].method).to.equal('GET');
+    expect(xhrRequests[1].url).to.equal(
+      'https://www.stage.adobe.com/lana/ll?m=Invalid%20severity%20test&c=testClientId&s=100&t=e&r=d&d',
+    );
+    expect(console.warn.called).to.be.true;
+    expect(console.warn.args[0][0]).to.include('Invalid severity');
+    expect(console.warn.args[0][0]).to.include('Defaulting to \'d\'');
+  });
+
+  it('prevents XSS by properly encoding message content', () => {
+    // Test with a string containing characters that should be encoded for XSS prevention
+    // According to standards, encodeURIComponent does NOT encode: A-Z a-z 0-9 - _ . ! ~ * ' ( )
+    // But it DOES encode: < > " & ; , / ? : @ & = + $ #
+    const maliciousString = '<>"&;,/?:@=+$#';
+
+    // Create a reference properly encoded string using the standard function
+    const properlyEncoded = encodeURIComponent(maliciousString);
+
+    window.lana.log(maliciousString);
+    expect(xhrRequests.length).to.equal(1);
+
+    // Verify the URL contains properly encoded values
+    const { url } = xhrRequests[0];
+    const msgParam = url.match(/m=([^&]*)/)[1];
+
+    // The encoded message should match what encodeURIComponent would produce
+    expect(msgParam).to.equal(properlyEncoded);
+
+    // Specifically check that key characters are encoded in the message parameter
+    // Note: We're checking msgParam, not the entire URL,
+    // because & is a valid URL parameter separator
+    expect(msgParam).not.to.include('<');
+    expect(msgParam).not.to.include('>');
+    expect(msgParam).not.to.include('"');
+    expect(msgParam).not.to.include('&');
+
+    // Verify encoded versions exist in the message parameter
+    expect(msgParam).to.include('%3C'); // < encoded
+    expect(msgParam).to.include('%3E'); // > encoded
+    expect(msgParam).to.include('%22'); // " encoded
+    expect(msgParam).to.include('%26'); // & encoded
+
+    // Double-check that decoding gets back the original string
+    const decodedMsg = decodeURIComponent(msgParam);
+    expect(decodedMsg).to.equal(maliciousString);
+  });
 });
diff --git a/test/utils/loadLana.test.js b/test/utils/loadLana.test.js
index 2968244127e..d9a687af52b 100644
--- a/test/utils/loadLana.test.js
+++ b/test/utils/loadLana.test.js
@@ -1,28 +1,56 @@
 import { expect } from '@esm-bundle/chai';
 import sinon from 'sinon';
-import { waitFor } from '../helpers/waitfor.js';
 import { loadLana } from '../../libs/utils/utils.js';
 
 describe('Utils loadLana', () => {
+  let originalLocation;
+
+  beforeEach(() => {
+    originalLocation = window.location.href;
+    // Make sure console.log isn't already wrapped
+    if (console.log.restore && typeof console.log.restore === 'function') {
+      console.log.restore();
+    }
+  });
+
+  afterEach(() => {
+    // Reset URL if it was changed
+    if (window.location.href !== originalLocation) {
+      window.history.pushState({}, '', originalLocation);
+    }
+
+    // Always restore console.log if it was spied on
+    if (console.log.restore && typeof console.log.restore === 'function') {
+      console.log.restore();
+    }
+
+    // Reset lana for the next test
+    delete window.lana;
+  });
+
   it('Loads lana.js upon calling lana.log the first time', async () => {
     expect(window.lana?.log).not.to.exist;
     loadLana();
     expect(window.lana.log).to.exist;
 
+    // Store the initial placeholder log function
     const initialLana = window.lana.log;
+
+    // Spy on console.log before making the first call
     sinon.spy(console, 'log');
+
+    // Call lana.log which should trigger dynamic import
+    // This returns a Promise that resolves after the real lana.js is loaded
     await window.lana.log('test', { clientId: 'myclient', sampleRate: 0 });
-    await waitFor(() => initialLana !== window.lana.log);
 
+    // After the import, window.lana.log should be replaced with the real implementation
+    expect(window.lana.log).not.to.equal(initialLana);
     expect(window.lana.options).to.exist;
-    expect(console.log.args[0][0]).to.equal('LANA Msg: ');
-    expect(console.log.args[0][1]).to.equal('test');
-    console.log.restore();
+    expect(console.log.called).to.be.true;
 
-    sinon.spy(console, 'log');
-    await window.lana.log('test2', { clientId: 'myclient', sampleRate: 0 });
-    expect(console.log.args[0][0]).to.equal('LANA Msg: ');
-    expect(console.log.args[0][1]).to.equal('test2');
-    console.log.restore();
+    // Check the logging arguments
+    const firstCallArgs = console.log.getCall(0).args;
+    expect(firstCallArgs[0]).to.equal('LANA Msg: ');
+    expect(firstCallArgs[1]).to.equal('test');
   });
 });


Reviews:
user: login: github-actions[bot]
body: Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

<details>
<summary>eslint</summary>

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L75-L75

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L81-L81

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L84-L84

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L88-L88

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L91-L91

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L93-L93

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L94-L94

<hr>

🚫 **[eslint]** <[no-multi-spaces](https://eslint.org/docs/rules/no-multi-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Multiple spaces found before '// Explicitly ...'.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L96-L96

<hr>

🚫 **[eslint]** <[comma-dangle](https://eslint.org/docs/rules/comma-dangle)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Missing trailing comma.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L96-L96

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L99-L99

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/d6aeb8fd465e8792f54dbaf28f31057ace028983/test/utils/loadLana.test.js#L103-L103

</details>

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T19:14:06Z
user: login: github-actions[bot]
body: Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

<details>
<summary>eslint</summary>

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L65-L65

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L68-L68

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L72-L72

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L75-L75

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L81-L81

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L84-L84

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L88-L88

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L91-L91

<hr>

🚫 **[eslint]** <[no-multi-spaces](https://eslint.org/docs/rules/no-multi-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Multiple spaces found before '// Explicitly ...'.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L96-L96

<hr>

🚫 **[eslint]** <[comma-dangle](https://eslint.org/docs/rules/comma-dangle)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Missing trailing comma.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L96-L96

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L99-L99

<hr>

🚫 **[eslint]** <[no-trailing-spaces](https://eslint.org/docs/rules/no-trailing-spaces)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Trailing spaces not allowed.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L103-L103

<hr>

🚫 **[eslint]** <[eol-last](https://eslint.org/docs/rules/eol-last)> <sub>reported by [reviewdog](https://github.com/reviewdog/reviewdog) :dog:</sub><br>Newline required at end of file but not found.

https://github.com/adobecom/milo/blob/ff997c668275772aea363908e9ec6a95857af1ee/test/utils/loadLana.test.js#L106

</details>

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T19:44:43Z
user: login: chrischrischris
body: Looks good.  Maybe just normalize the severities so searches are easier in splunk.

Other than the comment for `errorType` I think all the other comments can be removed as the code is self-explanatory - also since this file is loaded early in page load we should keep it as tiny as possible.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:22:03Z
user: login: thedoc31
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:50:49Z
user: login: thedoc31
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T15:51:11Z
user: login: 3ch023
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T07:42:25Z
user: login: thedoc31
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:19:14Z
user: login: JackySun9
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:03:24Z

Review comments:
diff_hunk: @@ -64,6 +80,24 @@
       return;
     }
 
+    // Process severity only if it's explicitly provided in original options
path: libs/utils/lana.js
user: login: chrischrischris
body: super nit: comments aren't needed in this new block of code
created_at: 2025-04-01T15:18:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +80,24 @@
       return;
     }
 
+    // Process severity only if it's explicitly provided in original options
+    let severity;
+    if (options && options.severity !== undefined) {
+      // Check if value is valid
+      if (VALID_SEVERITIES.has(options.severity)) {
+        severity = options.severity;
path: libs/utils/lana.js
user: login: chrischrischris
body: Do we want to normalize the severity to be single letters?  E.g:
```suggestion
        severity = options.severity[0];
```
created_at: 2025-04-01T15:20:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +80,24 @@
       return;
     }
 
+    // Process severity only if it's explicitly provided in original options
+    let severity;
+    if (options && options.severity !== undefined) {
+      // Check if value is valid
+      if (VALID_SEVERITIES.has(options.severity)) {
+        severity = options.severity;
path: libs/utils/lana.js
user: login: thedoc31
body: We don't need to normalize within Milo; the Lana back-end code will take whatever is sent in and convert it to the full word for recording in splunk (e.g. `warn`, `critical`, etc).
created_at: 2025-04-01T15:50:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -64,6 +80,24 @@
       return;
     }
 
+    // Process severity only if it's explicitly provided in original options
path: libs/utils/lana.js
user: login: thedoc31
body: Removed all the new comments since size is a consideration for this file.
created_at: 2025-04-01T15:51:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,6 +29,10 @@ export class MerchSideNav extends LitElement {
                 z-index: 2;
             }
 
+            :host h2 {
path: libs/features/mas/src/sidenav/merch-sidenav.js
user: login: 3ch023
body: looks liks some issue with rebase, i assume this change doesn't belong to this PR
created_at: 2025-04-07T07:42:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,6 +29,10 @@ export class MerchSideNav extends LitElement {
                 z-index: 2;
             }
 
+            :host h2 {
path: libs/features/mas/src/sidenav/merch-sidenav.js
user: login: thedoc31
body: Yeah, issue with rebase. I only modified lana.js and its unit tests. I don't see that in the PR any longer, unless i missed something. 
created_at: 2025-04-07T19:19:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: aem-code-sync[bot]
created_at: 2025-03-10T16:48:50Z
body: <!-- aem-bot-dash -->
Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch and validate page speed.
In case there are problems, just click a checkbox below to rerun the respective action.

- [ ] <!-- action-psi --> Re-run PSI checks
- [ ] <!-- action-sync --> Re-sync branch

<details>
<summary>Commits</summary>
<!-- commit-history -->

* [b8d4b31](https://github.com/adobecom/milo/commit/b8d4b31cf7f3ea14ca7b4dba29b8d55795dd6efb) :white_check_mark: (latest)
* [fd9e583](https://github.com/adobecom/milo/commit/fd9e5832600cc7a76301a57129f5d5364ff28c82) :white_check_mark:
* [10b0d30](https://github.com/adobecom/milo/commit/10b0d30dfee970d964f601dea2f0bee80e38533c) :white_check_mark:
* [b295e69](https://github.com/adobecom/milo/commit/b295e694f1f52dd4479af6a0e62e1322faa01a58) :white_check_mark:
* [6c10136](https://github.com/adobecom/milo/commit/6c101361e3066194350ab7af7782948223900b00) :white_check_mark:
* [e77b9b7](https://github.com/adobecom/milo/commit/e77b9b7c085ac368b885b91f03448fec865dbd76) :white_check_mark:
* [2ac1697](https://github.com/adobecom/milo/commit/2ac1697c046a99cf198189ca187c4fee85ee7dd7) :white_check_mark:
* [084b6d2](https://github.com/adobecom/milo/commit/084b6d28eeb4c251debfbc1c2711e8b875ec62ec) :white_check_mark:
* [cc7927d](https://github.com/adobecom/milo/commit/cc7927dbb08090bd30ca031f5f2454f5647efcd9) :white_check_mark:
* [16f41a5](https://github.com/adobecom/milo/commit/16f41a55061547e1a32ee557691610678bd78a94) :white_check_mark:
* [d1ba671](https://github.com/adobecom/milo/commit/d1ba6717bdce41b82c01a479150daa63a36b6cfe) :white_check_mark:
* [ff997c6](https://github.com/adobecom/milo/commit/ff997c668275772aea363908e9ec6a95857af1ee) :white_check_mark:
* [d6aeb8f](https://github.com/adobecom/milo/commit/d6aeb8fd465e8792f54dbaf28f31057ace028983) :white_check_mark:
* [153bbf4](https://github.com/adobecom/milo/commit/153bbf40ee94fd4978464d82a5dba64e7ca535ad) :white_check_mark:

<!-- commit-history-end -->
</details>
author_association: CONTRIBUTOR
user: login: aem-code-sync[bot]
created_at: 2025-03-10T16:49:48Z
body: <!-- aem-bot-psi -->
| | Page | Scores | Audits | Google |
|-|------|--------|--------|--------|
| :iphone: | [/?martech=off](https://MWPW-167072--milo--adobecom.aem.page/?martech=off) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-100-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.4.0&SI=1911.6802239937783&FCP=855.5&LCP=1354&TBT=0&CLS=0 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-96-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-100-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-1.9s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.9s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-1.4s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2FMWPW-167072--milo--adobecom.aem.page%2F%3Fmartech%3Doff "Run again in Google PSI") |
| :desktop_computer: | [/?martech=off](https://MWPW-167072--milo--adobecom.aem.page/?martech=off) | [![PERFORMANCE](https://img.shields.io/badge/PERFORMANCE-100-green?style=for-the-badge "Performance")](https://googlechrome.github.io/lighthouse/scorecalc/#device=mobile&version=12.4.0&SI=550.1608248904438&FCP=265.5&LCP=810.4099249999999&TBT=0&CLS=0 "See Calculator") ![A11Y](https://img.shields.io/badge/A11Y-96-green?style=for-the-badge "Accessibility") ![SEO](https://img.shields.io/badge/SEO-69-orange?style=for-the-badge "SEO") ![BEST PRACTICES](https://img.shields.io/badge/BEST%20PRACTICES-96-green?style=for-the-badge "Best Practices") | ![SI](https://img.shields.io/badge/SI-0.6s-green?style=for-the-badge "Speed Index") ![FCP](https://img.shields.io/badge/FCP-0.3s-green?style=for-the-badge "First Contentful Paint") ![LCP](https://img.shields.io/badge/LCP-0.8s-green?style=for-the-badge "Largest Contentful Paint") ![TBT](https://img.shields.io/badge/TBT-0ms-green?style=for-the-badge "Total Blocking Time") ![CLS](https://img.shields.io/badge/CLS-0-green?style=for-the-badge "Cumulative Layout Shift") | [![PSI](https://www.gstatic.com/images/icons/material/product/2x/pagespeed_16dp.png)](https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2FMWPW-167072--milo--adobecom.aem.page%2F%3Fmartech%3Doff "Run again in Google PSI") |

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-18T01:40:05Z
body: This PR has not been updated recently and will be closed in 7 days if no action is taken. Please ensure all checks are passing, https://github.com/orgs/adobecom/discussions/997 provides instructions. If the PR is ready to be merged, please mark it with the "Ready for Stage" label.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-30T01:47:54Z
body: This PR has not been updated recently and will be closed in 7 days if no action is taken. Please ensure all checks are passing, https://github.com/orgs/adobecom/discussions/997 provides instructions. If the PR is ready to be merged, please mark it with the "Ready for Stage" label.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-02T01:45:58Z
body: This pull request is not passing all required checks. Please see [this discussion](https://github.com/orgs/adobecom/discussions/997) for information on how to get all checks passing. Inconsistent checks can be manually retried. If a test absolutely can not pass for a good reason, please add a comment with an explanation to the PR.
author_association: CONTRIBUTOR
user: login: rgclayton
created_at: 2025-04-04T22:55:47Z
body: Looks like you've pulled in other commits outside of what you worked on?
Unless you worked on `action-scroller.js`, etc thats listed in changed files?
author_association: CONTRIBUTOR
user: login: thedoc31
created_at: 2025-04-08T16:19:38Z
body: @rgclayton @3ch023 @overmyheadandbody Could one of you please approve so we can get this merged in?
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-16T01:45:06Z
body: This PR has not been updated recently and will be closed in 7 days if no action is taken. Please ensure all checks are passing, https://github.com/orgs/adobecom/discussions/997 provides instructions. If the PR is ready to be merged, please mark it with the "Ready for Stage" label.
author_association: CONTRIBUTOR