From 85883ce2cefa19be3e29c7e135f3b80f3c42f504 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Fri, 21 Mar 2025 12:35:12 +0000
Subject: [PATCH 01/13] chore: update ably-ui to 15.8.1

---
 package.json | 2 +-
 yarn.lock    | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/package.json b/package.json
index 9fe4475c7f..2cd25a13ec 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
     "no-githooks": "git config --unset core.hooksPath"
   },
   "dependencies": {
-    "@ably/ui": "15.7.0",
+    "@ably/ui": "15.8.1",
     "@codesandbox/sandpack-react": "^2.9.0",
     "@mdx-js/react": "^2.3.0",
     "@react-hook/media-query": "^1.1.1",
diff --git a/yarn.lock b/yarn.lock
index a76e7337b0..3daf60b441 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,10 +7,10 @@
   resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
   integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
 
-"@ably/ui@15.7.0":
-  version "15.7.0"
-  resolved "https://registry.yarnpkg.com/@ably/ui/-/ui-15.7.0.tgz#b95626272fc8ba43162878b8a6016a7f86264ce8"
-  integrity sha512-1FBs+S9NUa0/6U5kZaAo+BIi8VZDoN8yG3dUeUzZ9m48bvigDgUP+AQtGOSiItD5aP6TiSHWnatERk/J/jN0Tw==
+"@ably/ui@15.8.1":
+  version "15.8.1"
+  resolved "https://registry.yarnpkg.com/@ably/ui/-/ui-15.8.1.tgz#be59ffe07c0ce826c1061adbcb8ff2a75dcfd78c"
+  integrity sha512-o2wpZEHpXDeLH1i+YaEeBf2PxqnG3EzBmmqB4Lx80NjbpTXFY19wI25bZ53fUuFroU04Yj1UQ/T94Qmbld6wkg==
   dependencies:
     "@radix-ui/react-accordion" "^1.2.1"
     "@radix-ui/react-navigation-menu" "^1.2.4"

From 4264dc183fb45f588514b97b920fcf4da7c5168b Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Wed, 19 Mar 2025 16:55:12 +0000
Subject: [PATCH 02/13] fix: constrain codeblock, table and pre element max
 width

---
 src/components/Markdown/CodeBlock.tsx                  |  2 +-
 src/components/blocks/list/Dl/index.tsx                |  2 +-
 src/components/blocks/software/Pre.tsx                 |  6 ++++--
 .../blocks/software/__snapshots__/Pre.test.tsx.snap    | 10 +++++-----
 src/components/blocks/table/Table.tsx                  |  2 +-
 .../ConditionalChildrenLanguageDisplay.test.js.snap    |  2 +-
 6 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/components/Markdown/CodeBlock.tsx b/src/components/Markdown/CodeBlock.tsx
index 90e90aeb38..941a40f92c 100644
--- a/src/components/Markdown/CodeBlock.tsx
+++ b/src/components/Markdown/CodeBlock.tsx
@@ -29,7 +29,7 @@ export const CodeBlock: FC<{ children: React.ReactNode; language: string }> = ({
   };
 
   return (
-    <pre className="bg-cool-black text-white p-0 rounded-lg relative">
+    <pre className="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full">
       <div className="overflow-auto relative p-16 pr-32">
         <code
           className="ui-text-code"
diff --git a/src/components/blocks/list/Dl/index.tsx b/src/components/blocks/list/Dl/index.tsx
index 0dabe53d21..e72470bab4 100644
--- a/src/components/blocks/list/Dl/index.tsx
+++ b/src/components/blocks/list/Dl/index.tsx
@@ -6,7 +6,7 @@ import cn from '@ably/ui/core/utils/cn';
 
 const Dl = ({ data, attribs }: HtmlComponentProps<'dl'>) => {
   return (
-    <dl {...attribs} className={cn(attribs?.className, listDl, 'ui-text-p2')}>
+    <dl {...attribs} className={cn(attribs?.className, listDl, 'ui-text-p2 max-w-[calc(100vw-48px)] sm:max-w-full')}>
       <Html data={data} BlockWrapper={DlWrapper} />
     </dl>
   );
diff --git a/src/components/blocks/software/Pre.tsx b/src/components/blocks/software/Pre.tsx
index 16d547248f..b3e04a5324 100644
--- a/src/components/blocks/software/Pre.tsx
+++ b/src/components/blocks/software/Pre.tsx
@@ -64,12 +64,14 @@ const Pre = ({
     languages = getLanguagesSDKInterface(languages, selectedSDKInterfaceTab);
   }
 
+  const codeClassName = 'bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full';
+
   const hasCode =
     languages?.some((lang) => getTrimmedLanguage(lang) === pageLanguage) || pageLanguage === DEFAULT_LANGUAGE;
   const shouldDisplayTip = !hasCode && languages?.length !== undefined;
   const withModifiedClassname = {
     ...attribs,
-    className: `bg-cool-black text-white p-0 rounded-lg relative`,
+    className: codeClassName,
   };
 
   const dataTreatedAsCode = data && !isString(data) && every((element) => element.type === HtmlDataTypes.text, data);
@@ -79,7 +81,7 @@ const Pre = ({
     const stringToRender = reduce((acc, curr) => acc.concat((curr.data as string) ?? ''), '', data);
 
     return (
-      <pre {...attribs} className="bg-cool-black text-white p-0 rounded-lg relative overflow-hidden">
+      <pre {...attribs} className={codeClassName}>
         <div className="overflow-auto relative p-16">
           <MultilineCodeContent
             dataContainsKey={false}
diff --git a/src/components/blocks/software/__snapshots__/Pre.test.tsx.snap b/src/components/blocks/software/__snapshots__/Pre.test.tsx.snap
index 799f4277f4..c39734a60a 100644
--- a/src/components/blocks/software/__snapshots__/Pre.test.tsx.snap
+++ b/src/components/blocks/software/__snapshots__/Pre.test.tsx.snap
@@ -5,7 +5,7 @@ exports[`<Pre /> should render code block when no languages are passed 1`] = `
   class="my-32"
 >
   <pre
-    class="bg-cool-black text-white p-0 rounded-lg relative"
+    class="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full"
   >
     useVars = false;
   </pre>
@@ -17,7 +17,7 @@ exports[`<Pre /> should successfully render Code elements with language 1`] = `
   class="my-32"
 >
   <pre
-    class="bg-cool-black text-white p-0 rounded-lg relative"
+    class="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full"
   >
     <div
       class="border-b border-charcoal-grey w-full"
@@ -118,7 +118,7 @@ exports[`<Pre /> should successfully render code elements with  only Realtime la
   class="my-32"
 >
   <pre
-    class="bg-cool-black text-white p-0 rounded-lg relative"
+    class="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full"
   >
     <div
       class="bg-dark-grey border-charcoal-grey text-white border-b-4 rounded-t-lg flex justify-end"
@@ -248,7 +248,7 @@ exports[`<Pre /> should successfully render code elements with  only Rest langua
   class="my-32"
 >
   <pre
-    class="bg-cool-black text-white p-0 rounded-lg relative"
+    class="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full"
   >
     <div
       class="bg-dark-grey border-charcoal-grey text-white border-b-4 rounded-t-lg flex justify-end"
@@ -378,7 +378,7 @@ exports[`<Pre /> should successfully render code elements with both Rest and Rea
   class="my-32"
 >
   <pre
-    class="bg-cool-black text-white p-0 rounded-lg relative"
+    class="bg-cool-black text-white p-0 rounded-lg relative max-w-[calc(100vw-48px)] sm:max-w-full"
   >
     <div
       class="bg-dark-grey border-charcoal-grey text-white border-b-4 rounded-t-lg flex justify-end"
diff --git a/src/components/blocks/table/Table.tsx b/src/components/blocks/table/Table.tsx
index 8bc510f1ba..9160f20867 100644
--- a/src/components/blocks/table/Table.tsx
+++ b/src/components/blocks/table/Table.tsx
@@ -12,7 +12,7 @@ const Table = ({ data, attribs }: HtmlComponentProps<'table'>) => {
   }
 
   return (
-    <div className={tableContainer}>
+    <div className={cn(tableContainer, 'max-w-[calc(100vw-48px)] sm:max-w-full')}>
       <table className={cn('border-0 border-collapse mb-4 border-spacing-0 ui-text-p2 text-left', table)} {...attribs}>
         <Html data={data.filter((item) => item.type === HtmlDataTypes.tag)} />
       </table>
diff --git a/src/components/blocks/wrappers/__snapshots__/ConditionalChildrenLanguageDisplay.test.js.snap b/src/components/blocks/wrappers/__snapshots__/ConditionalChildrenLanguageDisplay.test.js.snap
index cfc7627a74..159ca3d19c 100644
--- a/src/components/blocks/wrappers/__snapshots__/ConditionalChildrenLanguageDisplay.test.js.snap
+++ b/src/components/blocks/wrappers/__snapshots__/ConditionalChildrenLanguageDisplay.test.js.snap
@@ -2,7 +2,7 @@
 
 exports[`Integration: ConditionalChildrenLanguageDisplay only displays one <dt><dd> pair of children from alternatives for parsed definition lists ConditionalChildrenLanguageDisplay displays the expected results from HTML data 1`] = `
 <dl
-  className="listDl ui-text-p2"
+  className="listDl ui-text-p2 max-w-[calc(100vw-48px)] sm:max-w-full"
 >
   
 	

From 00cbd8de367d327ebdef67d5f70cb305442f279b Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Wed, 19 Mar 2025 16:59:54 +0000
Subject: [PATCH 03/13] fix: pin redoc version to 2.4 over latest to prevent UI
 regressions

---
 src/components/Redoc/Loader.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Redoc/Loader.tsx b/src/components/Redoc/Loader.tsx
index 1474035829..3c1ca638a4 100644
--- a/src/components/Redoc/Loader.tsx
+++ b/src/components/Redoc/Loader.tsx
@@ -4,7 +4,7 @@ import { GoTopButton } from './GoTopButton';
 import { overrideMenuItemNavigation } from './utils';
 
 export const Loader = ({ specUrl }: { specUrl: string }) => {
-  const redocDependencyScript = '//cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js';
+  const redocDependencyScript = '//cdn.redoc.ly/redoc/v2.4.0/bundles/redoc.standalone.js';
   const options = {
     hideDownloadButton: true,
     scrollYOffset: 64,

From c4dd183209e69db4f83e5655880e94c85d1c94e2 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 12:28:00 +0000
Subject: [PATCH 04/13] fix: remove unneeded state code from header

---
 src/components/Layout/Header.tsx | 27 +--------------------------
 1 file changed, 1 insertion(+), 26 deletions(-)

diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index bcad48507c..68534b6e59 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useContext } from 'react';
+import React, { useContext } from 'react';
 import Icon from '@ably/ui/core/Icon';
 import AblyHeader from '@ably/ui/core/Header';
 import { SearchBar } from '../SearchBar';
@@ -10,7 +10,6 @@ type HeaderProps = {
 };
 
 const Header: React.FC<HeaderProps> = ({ searchBar = true }) => {
-  const [showMenu, setShowMenu] = useState(false);
   const userContext = useContext(UserContext);
   const sessionState = {
     ...userContext.sessionState,
@@ -33,30 +32,6 @@ const Header: React.FC<HeaderProps> = ({ searchBar = true }) => {
   //   }
   // };
 
-  useEffect(() => {
-    const handleResize = () => {
-      if (window.innerWidth >= 1040) {
-        setShowMenu(false);
-      }
-    };
-
-    window.addEventListener('resize', handleResize);
-    return () => window.removeEventListener('resize', handleResize);
-  }, []);
-
-  useEffect(() => {
-    if (showMenu) {
-      document.body.classList.add('overflow-hidden');
-    } else {
-      document.body.classList.remove('overflow-hidden');
-    }
-
-    // Cleanup on unmount
-    return () => {
-      document.body.classList.remove('overflow-hidden');
-    };
-  }, [showMenu]);
-
   return (
     <AblyHeader
       // TODO: reenable when examples are ready to be released

From 851beea4bd4429dbe4d72c2f267a75d21193b4ce Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 12:28:57 +0000
Subject: [PATCH 05/13] fix: integrate 'home' link into accordion nav

---
 src/components/Layout/LeftSidebar.tsx | 49 ++++++++++++++-------------
 1 file changed, 25 insertions(+), 24 deletions(-)

diff --git a/src/components/Layout/LeftSidebar.tsx b/src/components/Layout/LeftSidebar.tsx
index bb267a37de..65fb7e16f8 100644
--- a/src/components/Layout/LeftSidebar.tsx
+++ b/src/components/Layout/LeftSidebar.tsx
@@ -1,5 +1,5 @@
 import { useMemo, useState, useEffect, useRef } from 'react';
-import { useLocation } from '@reach/router';
+import { navigate, useLocation } from '@reach/router';
 import cn from '@ably/ui/core/utils/cn';
 import Accordion from '@ably/ui/core/Accordion';
 import Icon from '@ably/ui/core/Icon';
@@ -163,6 +163,18 @@ const constructProductNavData = (
     };
   });
 
+  // Add a Home entry at the start of navData if inHeader is true
+  if (inHeader) {
+    navData.unshift({
+      name: 'Home',
+      content: null,
+      onClick: () => {
+        navigate('/docs');
+      },
+      interactive: false,
+    });
+  }
+
   return navData;
 };
 
@@ -192,29 +204,18 @@ const LeftSidebar = ({ inHeader = false }: LeftSidebarProps) => {
   );
 
   return (
-    <>
-      {inHeader ? (
-        <a
-          href="/docs"
-          aria-label="Home"
-          className="flex w-full items-center focus-base text-neutral-1000 dark:text-neutral-300 hover:text-neutral-1100 active:text-neutral-1000 transition-colors h-40 ui-text-menu1 font-bold px-16 mt-16"
-        >
-          Home
-        </a>
-      ) : null}
-      <Accordion
-        ref={sidebarRef}
-        className={cn(
-          !inHeader && [sidebarAlignmentClasses, 'hidden md:block md:-mx-16'],
-          'overflow-y-auto',
-          hasScrollbar ? 'md:pr-8' : 'md:pr-16',
-        )}
-        style={sidebarAlignmentStyles}
-        id="left-nav"
-        data={productNavData}
-        {...commonAccordionOptions(null, activePage.tree[0]?.index, true, inHeader)}
-      />
-    </>
+    <Accordion
+      ref={sidebarRef}
+      className={cn(
+        !inHeader && [sidebarAlignmentClasses, 'hidden md:block md:-mx-16'],
+        'overflow-y-auto',
+        hasScrollbar ? 'md:pr-8' : 'md:pr-16',
+      )}
+      style={sidebarAlignmentStyles}
+      id="left-nav"
+      data={productNavData}
+      {...commonAccordionOptions(null, activePage.tree[0]?.index, true, inHeader)}
+    />
   );
 };
 

From 57e319a11b477082cb1e47ac6157948e3a563f32 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 12:29:38 +0000
Subject: [PATCH 06/13] fix: pin left nav sub-accordion header height to
 surrounding line height

---
 src/components/Layout/utils/nav.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Layout/utils/nav.ts b/src/components/Layout/utils/nav.ts
index 630d7720df..6e8ba68714 100644
--- a/src/components/Layout/utils/nav.ts
+++ b/src/components/Layout/utils/nav.ts
@@ -131,7 +131,7 @@ export const commonAccordionOptions = (
       {
         'my-12': topLevel && inHeader,
         'h-40 ui-text-menu1 !font-bold md:ui-text-menu4 px-16': topLevel,
-        'h-[1rem] ui-text-menu2 !font-semibold md:ui-text-menu4': !topLevel,
+        'min-h-[1.625em] md:min-h-[1.375em] ui-text-menu2 !font-semibold md:ui-text-menu4': !topLevel,
       },
     ),
     selectedHeaderCSS: '!text-neutral-1300 mb-8',

From ea494aa9ae66b723b48c58d0d180e6e44da36123 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 13:14:21 +0000
Subject: [PATCH 07/13] fix: scroll to headers in menus if they go out of view
 during a section expansion

---
 src/components/Layout/LeftSidebar.tsx | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/components/Layout/LeftSidebar.tsx b/src/components/Layout/LeftSidebar.tsx
index 65fb7e16f8..901ce7b94f 100644
--- a/src/components/Layout/LeftSidebar.tsx
+++ b/src/components/Layout/LeftSidebar.tsx
@@ -130,6 +130,26 @@ const constructProductNavData = (
     return {
       name: product.name,
       icon: activePageTree[0]?.page.name === product.name ? product.icon.open : product.icon.closed,
+      onClick: () => {
+        // When a product is clicked, find and scroll to any open accordion element
+        if (typeof document !== 'undefined') {
+          // Use setTimeout to ensure the DOM has updated after the click and animation has completed
+          setTimeout(() => {
+            const targetAccordion = window.innerWidth >= 1040 ? 'left-nav' : 'mobile-nav';
+            const menuContainer = document.getElementById(targetAccordion);
+            const openAccordion: HTMLElement | null = menuContainer
+              ? menuContainer.querySelector('[data-state="open"] > button')
+              : null;
+
+            if (openAccordion) {
+              menuContainer?.scrollTo({
+                top: openAccordion.offsetTop,
+                behavior: 'smooth',
+              });
+            }
+          }, 200);
+        }
+      },
       content: (
         <div key={product.name} className="flex flex-col gap-20 px-16 pt-12">
           {product.showJumpLink ? (
@@ -212,7 +232,7 @@ const LeftSidebar = ({ inHeader = false }: LeftSidebarProps) => {
         hasScrollbar ? 'md:pr-8' : 'md:pr-16',
       )}
       style={sidebarAlignmentStyles}
-      id="left-nav"
+      id={inHeader ? 'mobile-nav' : 'left-nav'}
       data={productNavData}
       {...commonAccordionOptions(null, activePage.tree[0]?.index, true, inHeader)}
     />

From a3f6d8ce64e7bd86b7d59c4725e48d6f0b83637b Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 16:28:26 +0000
Subject: [PATCH 08/13] fix: remove duplicated height calculation logic from
 ably-ui

---
 src/components/Layout/LanguageSelector.tsx |  9 ++-------
 src/components/Layout/RightSidebar.tsx     |  3 ++-
 src/components/Layout/utils/heights.ts     | 14 +-------------
 src/components/Layout/utils/nav.ts         |  2 +-
 4 files changed, 6 insertions(+), 22 deletions(-)

diff --git a/src/components/Layout/LanguageSelector.tsx b/src/components/Layout/LanguageSelector.tsx
index 637d538b10..b66c00c7b0 100644
--- a/src/components/Layout/LanguageSelector.tsx
+++ b/src/components/Layout/LanguageSelector.tsx
@@ -5,18 +5,13 @@ import Badge from '@ably/ui/core/Badge';
 import Icon from '@ably/ui/core/Icon';
 import { IconName } from '@ably/ui/core/Icon/types';
 import cn from '@ably/ui/core/utils/cn';
+import { componentMaxHeight, HEADER_BOTTOM_MARGIN, HEADER_HEIGHT } from '@ably/ui/core/utils/heights';
 import { languageData, languageInfo } from 'src/data/languages';
 import { LanguageKey } from 'src/data/languages/types';
 import { useOnClickOutside } from 'src/hooks';
 import { useLayoutContext } from 'src/contexts/layout-context';
 import { navigate } from '../Link';
-import {
-  componentMaxHeight,
-  HEADER_HEIGHT,
-  HEADER_BOTTOM_MARGIN,
-  LANGUAGE_SELECTOR_HEIGHT,
-  INKEEP_ASK_BUTTON_HEIGHT,
-} from './utils/heights';
+import { LANGUAGE_SELECTOR_HEIGHT, INKEEP_ASK_BUTTON_HEIGHT } from './utils/heights';
 
 type LanguageSelectorOptionData = {
   label: LanguageKey;
diff --git a/src/components/Layout/RightSidebar.tsx b/src/components/Layout/RightSidebar.tsx
index a8a0f6c527..60119b6f43 100644
--- a/src/components/Layout/RightSidebar.tsx
+++ b/src/components/Layout/RightSidebar.tsx
@@ -3,12 +3,13 @@ import { useLocation, WindowLocation } from '@reach/router';
 import cn from '@ably/ui/core/utils/cn';
 import Icon from '@ably/ui/core/Icon';
 import { IconName } from '@ably/ui/core/Icon/types';
+import { componentMaxHeight, HEADER_HEIGHT, HEADER_BOTTOM_MARGIN } from '@ably/ui/core/utils/heights';
 
 import { LanguageSelector } from './LanguageSelector';
 import { useLayoutContext } from 'src/contexts/layout-context';
 import { languageInfo } from 'src/data/languages';
 import { PageTreeNode, sidebarAlignmentClasses, sidebarAlignmentStyles } from './utils/nav';
-import { componentMaxHeight, HEADER_HEIGHT, HEADER_BOTTOM_MARGIN, INKEEP_ASK_BUTTON_HEIGHT } from './utils/heights';
+import { INKEEP_ASK_BUTTON_HEIGHT } from './utils/heights';
 
 type SidebarHeader = {
   id: string;
diff --git a/src/components/Layout/utils/heights.ts b/src/components/Layout/utils/heights.ts
index 788218ed2b..c0119bd371 100644
--- a/src/components/Layout/utils/heights.ts
+++ b/src/components/Layout/utils/heights.ts
@@ -3,18 +3,6 @@
   these values to prevent magic numbers popping up with no obvious reasoning. When making alterations
   to Layout components, consider these values and update where necessary.
 */
-export const HEADER_HEIGHT = 64;
-export const HEADER_BOTTOM_MARGIN = 24;
+
 export const LANGUAGE_SELECTOR_HEIGHT = 38;
 export const INKEEP_ASK_BUTTON_HEIGHT = 96;
-
-/**
- * Calculates the maximum height for a component by subtracting the total of given heights from 100vh.
- *
- * @param {...number} heights - An array of heights in pixels.
- * @returns {string} The CSS calc expression for the maximum height.
- */
-export const componentMaxHeight = (...heights: number[]): string => {
-  const totalHeight = heights.reduce((sum, height) => sum + height, 0) + 'px';
-  return `calc(100vh - ${totalHeight})`;
-};
diff --git a/src/components/Layout/utils/nav.ts b/src/components/Layout/utils/nav.ts
index 6e8ba68714..0cf19c92cf 100644
--- a/src/components/Layout/utils/nav.ts
+++ b/src/components/Layout/utils/nav.ts
@@ -1,8 +1,8 @@
 import cn from '@ably/ui/core/utils/cn';
 import { AccordionProps } from '@ably/ui/core/Accordion';
+import { HEADER_HEIGHT, componentMaxHeight } from '@ably/ui/core/utils/heights';
 import { ProductData, ProductKey } from 'src/data/types';
 import { NavProductContent, NavProductPage, NavProductPages } from 'src/data/nav/types';
-import { componentMaxHeight, HEADER_HEIGHT } from './heights';
 import { LanguageKey } from 'src/data/languages/types';
 
 export type PageTreeNode = { index: number; page: NavProductPage };

From 0183e2d5002d9e14652638b0cac84d1d8ecdd956 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 16:43:36 +0000
Subject: [PATCH 09/13] fix: pass location into header component and reinforce
 search button coloring

---
 src/components/Layout/Header.test.tsx | 8 ++++++++
 src/components/Layout/Header.tsx      | 5 ++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/components/Layout/Header.test.tsx b/src/components/Layout/Header.test.tsx
index 6b1b4e2b9f..6fd5fee377 100644
--- a/src/components/Layout/Header.test.tsx
+++ b/src/components/Layout/Header.test.tsx
@@ -29,6 +29,14 @@ jest.mock('./LeftSidebar', () => ({
   default: jest.fn(() => <div>LeftSidebar</div>),
 }));
 
+jest.mock('@reach/router', () => ({
+  useLocation: jest.fn(),
+}));
+
+jest.mock('./LanguageSelector', () => ({
+  LanguageSelector: jest.fn(() => <div>LanguageSelector</div>),
+}));
+
 describe('Header', () => {
   beforeEach(() => {
     document.body.innerHTML = '';
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index 68534b6e59..19cd7c2cec 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -1,4 +1,5 @@
 import React, { useContext } from 'react';
+import { useLocation } from '@reach/router';
 import Icon from '@ably/ui/core/Icon';
 import AblyHeader from '@ably/ui/core/Header';
 import { SearchBar } from '../SearchBar';
@@ -10,6 +11,7 @@ type HeaderProps = {
 };
 
 const Header: React.FC<HeaderProps> = ({ searchBar = true }) => {
+  const location = useLocation();
   const userContext = useContext(UserContext);
   const sessionState = {
     ...userContext.sessionState,
@@ -61,7 +63,7 @@ const Header: React.FC<HeaderProps> = ({ searchBar = true }) => {
       mobileNav={<LeftSidebar inHeader key="nav-mobile-documentation-tab" />}
       searchButton={
         <button
-          className="cursor-pointer focus-base rounded"
+          className="cursor-pointer focus-base rounded px-0 pt-4 text-neutral-1300 dark:text-neutral-000"
           aria-label="Toggle search"
           onClick={() => {
             const searchContainer = document.querySelector('#inkeep-search > div');
@@ -100,6 +102,7 @@ const Header: React.FC<HeaderProps> = ({ searchBar = true }) => {
       ]}
       sessionState={sessionState}
       logoHref="/docs"
+      location={location}
     />
   );
 };

From 043b691b3cd48d43ac61c98d2ea76727c862775e Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 17:32:58 +0000
Subject: [PATCH 10/13] chore: add further properties to sessionState test
 object

---
 src/components/Layout/Header.test.tsx | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/components/Layout/Header.test.tsx b/src/components/Layout/Header.test.tsx
index 6fd5fee377..b309f91966 100644
--- a/src/components/Layout/Header.test.tsx
+++ b/src/components/Layout/Header.test.tsx
@@ -100,7 +100,11 @@ describe('Header', () => {
         value={{
           sessionState: {
             signedIn: true,
-            account: { links: { dashboard: { href: '/dashboard', text: 'Dashboard' } } },
+            account: {
+              id: 'test-id',
+              name: 'Test Account',
+              links: { dashboard: { href: '/dashboard', text: 'Dashboard' } },
+            },
           },
           apps: [],
         }}

From a4544c198d2ad7dd3d6aca193478b29f5fad9854 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Thu, 20 Mar 2025 17:33:33 +0000
Subject: [PATCH 11/13] chore: ease the gapping between layout elements for
 more graceful responsivity

---
 src/components/Layout/Layout.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
index a03bd2bfa5..7d63075dc6 100644
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -24,7 +24,7 @@ const Layout: React.FC<LayoutProps> = ({ children, pageContext }) => {
   return (
     <GlobalLoading template={template}>
       <Header searchBar={searchBar} />
-      <div className="flex pt-64 gap-80 justify-center ui-standard-container mx-auto">
+      <div className="flex pt-64 md:gap-48 lg:gap-64 xl:gap-80 justify-center ui-standard-container mx-auto">
         {sidebar ? <LeftSidebar /> : null}
         <Container as="main" className="flex-1">
           {sidebar ? <Breadcrumbs /> : null}

From c373b3ea787f0668fabd369cb203ab63de4e0cfa Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Fri, 21 Mar 2025 13:00:46 +0000
Subject: [PATCH 12/13] fix: don't render duplicate breadcrumbs on product
 homepages

---
 src/components/Layout/Breadcrumbs.test.tsx | 25 ++++++++++++++++++++++
 src/components/Layout/Breadcrumbs.tsx      | 17 ++++++++++-----
 2 files changed, 37 insertions(+), 5 deletions(-)

diff --git a/src/components/Layout/Breadcrumbs.test.tsx b/src/components/Layout/Breadcrumbs.test.tsx
index 9395f1830b..6219fb7bf6 100644
--- a/src/components/Layout/Breadcrumbs.test.tsx
+++ b/src/components/Layout/Breadcrumbs.test.tsx
@@ -58,4 +58,29 @@ describe('Breadcrumbs', () => {
     expect(screen.getByText('Current Page')).toHaveClass('text-gui-unavailable');
     expect(screen.getByText('Current Page')).toHaveClass('pointer-events-none');
   });
+
+  it('removes duplicate links from breadcrumb nodes', () => {
+    mockUseLayoutContext.mockReturnValue({
+      activePage: {
+        tree: [
+          { page: { name: 'Section 1', link: '/section-1' } },
+          { page: { name: 'Duplicate Section', link: '/section-1' } },
+          { page: { name: 'Current Page', link: '/section-1/page-1' } },
+        ],
+      },
+    });
+
+    render(<Breadcrumbs />);
+
+    // Should only show one instance of the duplicate link
+    const section1Links = screen.getAllByText('Section 1');
+    expect(section1Links).toHaveLength(1);
+
+    // Should not render the duplicate with different text
+    expect(screen.queryByText('Duplicate Section')).not.toBeInTheDocument();
+
+    // Should still render other breadcrumb elements
+    expect(screen.getByText('Home')).toBeInTheDocument();
+    expect(screen.getByText('Current Page')).toBeInTheDocument();
+  });
 });
diff --git a/src/components/Layout/Breadcrumbs.tsx b/src/components/Layout/Breadcrumbs.tsx
index 0a6c146dc9..4d09220b1f 100644
--- a/src/components/Layout/Breadcrumbs.tsx
+++ b/src/components/Layout/Breadcrumbs.tsx
@@ -3,15 +3,22 @@ import { useLayoutContext } from 'src/contexts/layout-context';
 import Link from '../Link';
 import Icon from '@ably/ui/core/Icon';
 import cn from '@ably/ui/core/utils/cn';
-import { hierarchicalKey } from './utils/nav';
+import { hierarchicalKey, PageTreeNode } from './utils/nav';
 
 const Breadcrumbs: React.FC = () => {
   const { activePage } = useLayoutContext();
 
-  const breadcrumbNodes = useMemo(
-    () => activePage?.tree.filter((node) => node.page.link !== '#') ?? [],
-    [activePage.tree],
-  );
+  const breadcrumbNodes = useMemo(() => {
+    const filteredNodes = activePage?.tree.filter((node) => node.page.link !== '#') ?? [];
+    const uniqueNodes = filteredNodes.reduce((acc: PageTreeNode[], current) => {
+      const isDuplicate = acc.some((item) => item.page.link === current.page.link);
+      if (!isDuplicate) {
+        acc.push(current);
+      }
+      return acc;
+    }, []);
+    return uniqueNodes;
+  }, [activePage?.tree]);
 
   if (breadcrumbNodes.length === 0) {
     return null;

From 54dcb88e8245bdb005d4123dc5e102ec8d0e9be9 Mon Sep 17 00:00:00 2001
From: Jamie Henson <jamie.henson@ably.com>
Date: Fri, 21 Mar 2025 13:09:12 +0000
Subject: [PATCH 13/13] fix: fix positioning and touch func of right nav
 language selector

---
 src/components/Article/index.tsx           |  2 +-
 src/components/Layout/LanguageSelector.tsx | 43 +++++++++++++++-------
 2 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/src/components/Article/index.tsx b/src/components/Article/index.tsx
index c6033c1b1f..1aa0df84bb 100644
--- a/src/components/Article/index.tsx
+++ b/src/components/Article/index.tsx
@@ -2,7 +2,7 @@ import React, { FunctionComponent as FC } from 'react';
 import { ArticleFooter } from './ArticleFooter';
 
 const Article: FC<{ children: React.ReactNode }> = ({ children }) => (
-  <article className="flex-1 overflow-x-hidden">
+  <article className="flex-1 overflow-x-hidden relative z-10">
     {children}
     <ArticleFooter />
   </article>
diff --git a/src/components/Layout/LanguageSelector.tsx b/src/components/Layout/LanguageSelector.tsx
index b66c00c7b0..866ba8fe72 100644
--- a/src/components/Layout/LanguageSelector.tsx
+++ b/src/components/Layout/LanguageSelector.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useMemo, useRef, useState } from 'react';
+import { useEffect, useMemo, useRef, useState, MouseEvent, TouchEvent } from 'react';
 import { useLocation } from '@reach/router';
 import Select from 'react-select';
 import Badge from '@ably/ui/core/Badge';
@@ -33,6 +33,16 @@ const LanguageSelectorOption = ({ isOption, setMenuOpen, langParam, ...props }:
   const lang = languageInfo[props.data.label];
   const location = useLocation();
 
+  const handleClick = (e: MouseEvent<HTMLDivElement> | TouchEvent<HTMLDivElement>) => {
+    e.preventDefault();
+
+    if (isOption) {
+      navigate(`${location.pathname}?lang=${props.data.label}`);
+    }
+
+    setMenuOpen(!props.selectProps.menuIsOpen);
+  };
+
   return (
     <div
       className={cn(
@@ -41,13 +51,8 @@ const LanguageSelectorOption = ({ isOption, setMenuOpen, langParam, ...props }:
           'p-8 hover:bg-neutral-100 dark:hover:bg-neutral-1200 cursor-pointer': isOption,
         },
       )}
-      onClick={() => {
-        if (isOption) {
-          navigate(`${location.pathname}?lang=${props.data.label}`);
-        }
-
-        setMenuOpen(!props.selectProps.menuIsOpen);
-      }}
+      onClick={handleClick}
+      onTouchEnd={handleClick}
       role="menuitem"
     >
       <div className={cn('flex items-center gap-8', { 'flex-1': isOption })}>
@@ -106,8 +111,16 @@ export const LanguageSelector = () => {
     setSelectedOption(defaultOption);
   }, [langParam, options]);
 
+  const handleClick = (e: MouseEvent<HTMLButtonElement> | TouchEvent<HTMLButtonElement>) => {
+    e.preventDefault();
+    setMenuOpen(!menuOpen);
+  };
+
   return (
-    <div ref={selectRef} className="absolute top-0 right-0 md:relative w-full text-right md:text-left mb-24 focus-base">
+    <div
+      ref={selectRef}
+      className="md:relative w-full text-right md:text-left mb-24 focus-base -mt-4 md:mt-0 -mr-4 md:mr-0"
+    >
       <Select
         options={options}
         value={selectedOption}
@@ -129,10 +142,12 @@ export const LanguageSelector = () => {
           ),
           SingleValue: (props) => <LanguageSelectorOption {...props} setMenuOpen={setMenuOpen} langParam={langParam} />,
           IndicatorSeparator: null,
-          DropdownIndicator: (props) => (
+          DropdownIndicator: () => (
             <button
-              className="flex items-center pl-8 text-red-orange"
-              onClick={() => setMenuOpen(!props.selectProps.menuIsOpen)}
+              role="button"
+              className="flex items-center pl-8 text-red-orange cursor-pointer"
+              onClick={handleClick}
+              onTouchEnd={handleClick}
               aria-label="Toggle language dropdown"
             >
               <Icon
@@ -156,7 +171,9 @@ export const LanguageSelector = () => {
               }}
               role="menu"
             >
-              <p className="ui-text-overline2 py-16 px-8 text-neutral-700 dark:text-neutral-600">Code Language</p>
+              <p className="ui-text-overline2 text-left py-16 px-8 text-neutral-700 dark:text-neutral-600">
+                Code Language
+              </p>
               {children}
             </div>
           ),