From ca0a3bfc8e0efa4c388401ee19e24bd4fbf848f7 Mon Sep 17 00:00:00 2001 From: Andrea De Pirro Date: Fri, 18 Sep 2026 16:50:28 +0200 Subject: [PATCH 1/2] fix: route API sidebar links through HTMX instead of full page reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API paths (`/docs/api/…`) were classified as external URLs in `assemblerStrategy.isExternalDocsUrl`, so every sidebar click on an API page bypassed HTMX and called `window.location.assign`. That caused a full-page reload — the top nav and sidebar visibly redrew and the accordion state reset on every navigation. Returning `false` from `isExternalDocsUrl` lets HTMX handle these links. The existing `shouldRetargetApiContentSwap` logic then swaps only `#api-content-grid`, leaving `#pages-nav` and the secondary nav untouched. Co-Authored-By: Claude Sonnet 4.6 --- .../web-components/shared/htmx/strategies/assembler.test.ts | 4 ++-- .../Assets/web-components/shared/htmx/strategies/assembler.ts | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.test.ts b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.test.ts index f0ad6e9de7..bc2fc61ac1 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.test.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.test.ts @@ -19,12 +19,12 @@ describe('assemblerStrategy with a non-default rootPath', () => { expect(assemblerStrategy.getPathFromUrl('/pricing')).toBe(null) }) - it('still excludes the /api sub-app under the prefixed root', () => { + it('allows the /api sub-app under the prefixed root through HTMX', () => { expect( assemblerStrategy.isExternalDocsUrl( '/elastic/docs-builder/docs/3634/api/elasticsearch' ) - ).toBe(true) + ).toBe(false) }) it('accepts absolute self-links on non-elastic.co hosts (previews)', () => { diff --git a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.ts b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.ts index bfc5867357..719b1b54b2 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.ts +++ b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/strategies/assembler.ts @@ -4,13 +4,11 @@ import type { HtmxUrlStrategy } from './types' // rootPath is '/docs' on prod but varies on PR previews (e.g. // '/elastic/docs-builder/docs/3634'), so it can't be hardcoded. const root = config.rootPath.replace(/\/$/, '') -const apiRoot = `${root}/api` const isDocsPath = (path: string) => path === root || path.startsWith(`${root}/`) export const assemblerStrategy: HtmxUrlStrategy = { - isExternalDocsUrl: (url) => - url === apiRoot || url.startsWith(`${apiRoot}/`), + isExternalDocsUrl: () => false, getPathFromUrl: (url) => { try { From 91c2f6acd18f40ff598439a1d0bc95683554ce07 Mon Sep 17 00:00:00 2001 From: Andrea De Pirro Date: Fri, 18 Sep 2026 17:11:56 +0200 Subject: [PATCH 2/2] fix: update web component tests for HTMX-routed API paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `useHtmxLink` and `useHtmxContainer` tests expected `hx-disable` on `/docs/api/…` paths. Since `isExternalDocsUrl` now returns `false` for all docs paths, API links are treated as internal: `htmx.process` is called and no `hx-disable` attribute is set. Co-Authored-By: Claude Sonnet 4.6 --- .../shared/htmx/useHtmxContainer.test.tsx | 22 +++++++++---------- .../shared/htmx/useHtmxLink.test.tsx | 18 +++++++-------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxContainer.test.tsx b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxContainer.test.tsx index 0c94ad5773..af7f6320fc 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxContainer.test.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxContainer.test.tsx @@ -69,8 +69,8 @@ describe('useHtmxContainer', () => { }) }) - describe('/docs/api paths should be disabled', () => { - it('should add hx-disable for /docs/api paths', () => { + describe('/docs/api paths should be enabled', () => { + it('should call htmx.process for /docs/api paths', () => { render( ) @@ -79,11 +79,11 @@ describe('useHtmxContainer', () => { const anchor = container.querySelector('a') expect(anchor).toHaveAttribute('href', '/docs/api/elasticsearch') - expect(anchor).toHaveAttribute('hx-disable', 'true') - expect(mockHtmx.process).not.toHaveBeenCalled() + expect(anchor).not.toHaveAttribute('hx-disable') + expect(mockHtmx.process).toHaveBeenCalledWith(container) }) - it('should add hx-disable for full elastic.co/docs/api URLs', () => { + it('should call htmx.process for full elastic.co/docs/api URLs', () => { render( ) @@ -92,8 +92,8 @@ describe('useHtmxContainer', () => { const anchor = container.querySelector('a') expect(anchor).toHaveAttribute('href', '/docs/api/kibana') - expect(anchor).toHaveAttribute('hx-disable', 'true') - expect(mockHtmx.process).not.toHaveBeenCalled() + expect(anchor).not.toHaveAttribute('hx-disable') + expect(mockHtmx.process).toHaveBeenCalledWith(container) }) }) @@ -184,9 +184,9 @@ describe('useHtmxContainer', () => { expect(internalLink).toHaveAttribute('href', '/docs/elasticsearch') expect(internalLink).not.toHaveAttribute('hx-disable') - // API link - htmx disabled + // API link - htmx enabled expect(apiLink).toHaveAttribute('href', '/docs/api/kibana') - expect(apiLink).toHaveAttribute('hx-disable', 'true') + expect(apiLink).not.toHaveAttribute('hx-disable') // External link - htmx disabled expect(externalLink).toHaveAttribute( @@ -199,12 +199,12 @@ describe('useHtmxContainer', () => { expect(mockHtmx.process).toHaveBeenCalledWith(container) }) - it('should not call htmx.process if all links are external', () => { + it('should not call htmx.process if all links are truly external', () => { render( API GitHub + Products `} /> ) diff --git a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxLink.test.tsx b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxLink.test.tsx index 4866b1e0f4..d86e16e782 100644 --- a/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxLink.test.tsx +++ b/src/Elastic.Documentation.Site/Assets/web-components/shared/htmx/useHtmxLink.test.tsx @@ -34,14 +34,14 @@ const TestLink = ({ url }: { url: string }) => { } describe('isExternalDocsUrl', () => { - it('should return true for /docs/api', () => { - expect(isExternalDocsUrl('/docs/api')).toBe(true) + it('should return false for /docs/api', () => { + expect(isExternalDocsUrl('/docs/api')).toBe(false) }) - it('should return true for /docs/api/ paths', () => { - expect(isExternalDocsUrl('/docs/api/')).toBe(true) - expect(isExternalDocsUrl('/docs/api/elasticsearch')).toBe(true) - expect(isExternalDocsUrl('/docs/api/kibana/some/path')).toBe(true) + it('should return false for /docs/api/ paths', () => { + expect(isExternalDocsUrl('/docs/api/')).toBe(false) + expect(isExternalDocsUrl('/docs/api/elasticsearch')).toBe(false) + expect(isExternalDocsUrl('/docs/api/kibana/some/path')).toBe(false) }) it('should return false for regular docs paths', () => { @@ -238,13 +238,13 @@ describe('useHtmxLink', () => { expect(anchor).not.toHaveAttribute('hx-disable') }) - it('should add hx-disable for /docs/api paths', () => { + it('should call htmx.process for /docs/api paths', () => { render() const anchor = screen.getByTestId('test-link') - expect(anchor).toHaveAttribute('hx-disable', 'true') - expect(mockHtmx.process).not.toHaveBeenCalled() + expect(anchor).not.toHaveAttribute('hx-disable') + expect(mockHtmx.process).toHaveBeenCalledWith(anchor) }) it('should add hx-disable for external URLs', () => {