0.1.25 - #23
Merged
Merged
Conversation
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
A tab whose directory holds only its _index (no child pages) rendered an empty left nav — and an empty mobile drawer panel, leaving the tab unreachable on mobile (drawer chips swap panels client-side rather than navigating). render-sidebar-tree now falls back at depth 0 to a link to the landing itself. Verified against the v3/changelog fixture + regression test in docs-tabs.spec.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Section (Kubernetes/Standalone) and version chips are real links to a different content tree, so tapping them reloads the page and would close the drawer mid-selection. mobile-nav.js now flags the tap in sessionStorage and re-opens the drawer on the next load, so a reader can pick section -> version -> topic without the drawer closing between selections. Topic links and tab chips don't set the flag, so tapping a topic still closes it. Verified with a browser spec (reopen-on-load + one-shot flag clear). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Replaces the reload+reopen approach: tapping a section (Kubernetes/Standalone) or version chip now fetches that tree's drawer nav and swaps it into the open drawer in place — no page reload — so a reader can keep picking section -> version -> topic without the drawer closing. The scroller and tab-swap wiring are refactored behind bindDrawer(root) so they re-bind after each swap; any fetch failure falls back to plain navigation. Adds a brief .drawer-loading dim and a browser spec (in-place swap, no navigation, drawer stays open). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io> # Conflicts: # CHANGELOG.md
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
Signed-off-by: Kristin Brown <kristin.brown@solo.io>
artberger
approved these changes
Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[v0.1.25] — 2026-08-03
Change —
docTabsmoves from a front-mattertabmodel to a content-directory (id) model (layouts/_partials/docs-tabs.html,layouts/partials/sidebar.html,hugo-*.toml,USAGE.md)docTabsprototype grouped pages by a front-mattertab: "<name>"key, re-derived the grouping by filtering the whole tree on every render, matched the active tab by URL string (which drifts between the OSS, enterprise, and local-dev URL shapes), and — because a tab was a virtual grouping, not a real container — linked each tab to its first item's page instead of a stable landing.id(a top-level directory under the version root) plus itsname, the sidebar roots the left-nav tree inside that directory, the active tab resolves by page ancestry (.IsDescendant) so every URL shape agrees, and each tab links to its directory_index. The enable gate becomes "2+ present tab directories in this version"; below that the band is suppressed and the full tree renders as before. Two supporting fixes: a tab holding only its_indexnow emits a link to its own landing (an empty pre-rendered panel made the mobile chip a dead end), opt-in per call site so non-tab sidebars are untouched; and the band centers its tab row in the sameutils/page-widthcontainer as the content column, so tabs align with the sidebar/content left edge at everypage.width.[[params.docTabs]]now requires anidper tab, and enablingdocTabsmeans partitioning that version's content into one top-level directory per tab; thetab:front-matter key is no longer read. No production consumer has enableddocTabs, so nothing in production changes — this supersedes the prototype before its first real use.USAGE.mdis rewritten to the directory model.make server-oss, then compare/test/v3/api/authentication/(thev3version partitions intodocumentation/,api/,changelog/, so three tabs render and the nav is rooted in the active tab's directory) with/test/main/everything/(no tab dirs → no band, full tree). Verified withhugo160OSS + enterprise builds; the rewrittentests/docs-tabs.spec.tspins both the enabled and disabled states plus the page-width wrapper, 9/9 on both brands. Takes effect when a consumer setsparams.docTabswithids, partitions its content, and bumps its extras pin.Add — per-tab
hideSidebardrops the left nav on adocTabstab, desktop only (layouts/_partials/docs-tabs.html,layouts/partials/sidebar.html,assets/css/docs-theme-extras.css,hugo-*.toml,tests/docs-tabs-sidebar.spec.ts,USAGE.md)docTabs, the left nav is rooted inside the active tab's directory — so a tab that owns a single page (a one-page changelog, one generated API reference) renders a one-item nav whose only link is the page the reader is already on, spending a 16rem column to say nothing. There was no way to say "this tab has no tree"; the choice was a full nav on every tab or no tabs at all.[[params.docTabs]]takes an optionalhideSidebar = true.docs-tabs.htmlresolves it for the ACTIVE tab (off the present-tab list, so a tab whose directory is missing in this version can't suppress a nav for pages it doesn't own) and puts it on the page store;sidebar.htmlturns it into asidebar-desktop-hiddenclass on the<aside>; one rule in the existing@media (min-width: 1280px)block hides it.display: nonerather thanvisibility/width, so the flex row gives the column back and the article widens instead of showing a blank gutter. Absent orfalse→ nav renders, which is every existing tab, so nothing changes until a tab opts in.xlthe same<aside>is the slide-out drawer, and the drawer is the only route to the tab chips, the version chips, and the other tabs' pre-rendered trees — hiding it on a phone would strand the reader on the current page with no way off it. So the suppression is CSS inside the desktop media query and the markup is always emitted; skipping the markup instead (the obvious first implementation) would have killed the drawer. The desktop tab band also stays, since it's how a reader gets from a nav-less tab back to one that has a nav.isset/default. Read as a plain truth test..hideSidebar | default falsewould misreport an explicitfalsethe same as unset, and Hugo'sissethas bitten this repo before (thewarnMissingDescriptionopt-out needed a case-sensitivity fix), so neither is used. Verified: settinghideSidebar = falsein the fixture fails the new tests exactly as removing the key does.docTabsis still enabled in no consumer, so this ships dormant alongside the directory-model change above. Observable on the theme fixture:make server-oss, then compare/test/v3/changelog/(the Changelog tab setshideSidebar = true: no left nav at laptop width, article reclaims the column, band still there) with/test/v3/api/authentication/(same config, no flag → nav intact). Narrow the window below 1280px on the changelog page and the drawer opens with all three tab chips and a tappable tree.hugo160OSS + enterprise builds. Newtests/docs-tabs-sidebar.spec.ts(browser project — the behavior is CSS, which the HTML-onlystaticproject cannot see) asserts the desktop hide, the reclaimed column, that an unflagged tab keeps its nav, and that the drawer still opens with its chips and tree at 390px;docs-tabs.spec.tsgains a static check that the marker class lands on the flagged tab's pages and only those. docs-tabs specs 14/14 on both brands, and the fullstatic+browsersuites stay green (1438 passed on enterprise). Both guards were checked by breaking them: deleting the CSS rule fails the two desktop tests and leaves the mobile one passing; flipping the flag tofalsefails the static marker test too.Add — the mobile drawer AJAX-swaps a section/version selection in place (
assets/js/mobile-nav.js,assets/css/docs-theme-extras.css,tests/mobile-drawer.spec.ts,playwright.config.ts)mobile-nav.jsnow fetches the target page, lifts its.sidebar-mobile-panelnav out of the response, and swaps it into the open drawer, so the reader keeps drilling down; a brief.drawer-loadingdim covers the fetch, and any fetch/parse failure falls back to plain navigation so a chip always does something. Drawer wiring moved behind a singlebindDrawer(root)so listeners re-attach to the swapped-in markup..sidebar-mobile-header— the chips sit above the dimmed nav wrapper, so they stayed clickable during a fetch. (3) Focus moves to the swapped-in.sidebar-nav; replacingpanel.innerHTMLdropped focus to<body>. (4) Closing the drawer restores the pre-swap markup, so a reader who peeks at another version and closes without picking a topic doesn't reopen onto a tree they aren't on (commit-on-close was rejected: it would navigate a reader who only wanted to look). The close is caught with aMutationObserveron the panel class rather than by wrappingtoggleMobileSidebar/closeMobileSidebar, which consumers' own nav templates call and manipulate. (5) Per-bindingAbortControllerteardown: thewindowresizelistener and per-rowResizeObserverare the two registrations that survive aninnerHTMLswap, and they accumulated for as long as a reader kept hopping. (6) A 5s timeout on the swap fetch..drawer-loadingdims the header and nav wrapper and turns off their pointer events, with no spinner, so a fetch that neither resolves nor rejects — a phone that loses signal mid-tap — left the drawer dimmed and input-locked indefinitely. (Not a hard trap: the overlay is a sibling of the panel, so tap-outside still closed it, but the reader had to discover that.) The abort lands in the samecatchas any other failure, so it reuses the navigate fallback rather than adding a second recovery path.AbortController+setTimeoutrather thanAbortSignal.timeout, which is newer than the APIs this file already feature-detects and would throw on a missing property instead of degrading to no timeout; the timer clears only after the body is read, so a response that starts streaming and then stalls also falls back. (7) Crossing UP past the breakpoint with the drawer open now closes it.restoreDrawerfires on the open→closed class edge and nothing was watching the viewport, so a swap at 390px followed by a widen past 1280px (tablet rotation, a desktop window drag) promoted the swapped tree into the desktop sidebar while the page, its URL, and the navbar version dropdown all still belonged to the original version — the drawer's deliberately temporary divergence made permanent. The resize handler calls the existingcloseMobileSidebar, so clearing.mobile-sidebar-openis the same edge the observer already watches and the reset falls out of the path that was there. That also fixes a second, pre-existing symptom:.sidebar-mobile-overlay.activecarries no media query, so the full-screen scrim used to survive onto the desktop layout.xland above, where the tab band and version dropdown navigate normally. The breakpoint is now a singleDRAWER_MAXconstant inmobile-nav.jsinstead of three hardcoded1280comparisons that all had to agree with the CSS bound.hugo160OSS + enterprise builds and a newtests/mobile-drawer.spec.ts— seven tests at 390px covering the in-place swap, a second hop (the only thing that exercises thebindDrawerre-bind), the fetch-failure fallback, close-and-reopen reset,ResizeObserveraccumulation, a fetch that hangs rather than rejecting (route.abort()is immediate, so the existing failure test passed with no timeout at all — this one leaves the route unfulfilled and asserts the fallback happens anyway), and a widen past the breakpoint mid-swap; each fails with its guard disabled. The spec lives outsidebrowser.spec.ts(whose file-leveltest.skip(!EVERYTHING)would have skipped it on every real consumer) and discovers its target by crawling the built output, so it also runs against consumer builds: kgateway-oss 5/5, agentgateway-oss 4/5 (second hop legitimately skipped, only one hop target). Unversioned consumers have no version chip row and skip all five.npx serveresolves a URL whose final path segment contains a dot as a file, so a version-root landing like/docs/kubernetes/1.0.x/returns a directory listing with no scripts and every assertion vacuous — discovery skips dotted last segments (harness quirk only; the real sites serve those landings fine). Discovery also requires a chip markedsidebar-mobile-version-active, because archived trees are still built but dropped from the switcher, so nothing is active and there's no version to swap away from and back to.Fix — stop remapping
keepVersionversion blocks in reused content (layouts/_shortcodes/reuse.html,hugo-*.toml, fixture,tests/reuse-version-remap.spec.ts){{< version include-if="…" keepVersion="true" >}}block reused inside a{{< rebase >}}had itsinclude-iftokens silently rewritten by the OSS→enterprise remap, so an author-supplied enterprise mapping resolved to the wrong version or none.keepVersion="true"means "these are already enterprise tokens — don't remap." The keepVersion transform converts such a block to percent form with aversion="…"param, and v0.1.22 broadened the remap's Pass-1 anchor from angle-only to both forms (to fix theget_cookierows), which started matching those blocks too. Since enterprise tokens overlapossVersionvalues, the remap shifted the tokens, yielding empty, shifted, or doubled matches.rebase.htmlnever had this bug — it bulk-converts percent→angle before keepVersion runs, so its angle-only remap can't see these blocks.version="…"param (the keepVersion signature) has its condition attribute renamed to a remap-invisible token (include-if=→kvincif=,exclude-if=→kvexcif=, neither containing theclude-ifthe remap keys on), restored afterward. Genuine percent-form blocks likeget_cookiecarry noversion=param and still remap as v0.1.22 intended.layouts/_shortcodes/reuse.htmloverride that shadows this module file (it flattens reuse output to one line). The production defect is fixed by the identical edit to that override insolo-io/docs; this module change keeps the two copies in parity. A pin bump alone does NOT change hub output while the override exists.kubectl applycommand renderedrefs/heads//examples/httpbin.yaml(empty branch), 2.1.x rendered a doubledrefs/heads/v2.2.xv2.3.x/…, and 2.2.x renderedmain— the branch comes fromkgw-docs/versions/github-branch.md, which maps enterprise versions to branches with keepVersion. Verified with a localhugo160kgatewaybuild: the three pages now rendermain,v2.3.x, andv2.2.x, a site-wide sweep finds no empty or doubled branch tokens, and the v0.1.22get_cookieremap still works. The fixture now reproduces the production shape (thev1entry carriesossVersion = "v3", so the tokenv3— itself a fixture version — remaps tov1) with a matched pair of inline blocks differing only inkeepVersion;tests/reuse-version-remap.spec.tsasserts the plain block IS remapped onto v1 (the control, so the keepVersion assertion can't pass vacuously) while the keepVersion block stays on v3, and fails on all three counts without the guard. Takes effect on the hub via the override edit; for other consumers, on a pin bump.Fix — capped reference tables stop folding one character per line on phones (
assets/css/docs-theme-extras.css, fixture,tests/table-display.spec.ts).table-capped(render-table.htmlapplies it to any table with 3+ columns) collapsed into an unreadable vertical strip on phones: at 375px,table-layout:auto; width:100%squeezes the columns toward nothing and theoverflow-wrap: anywherecap folds the content — measured on the fixture at 1 character per line over 50 lines for a registry token and 1 over 146 for a prose Description cell.767pxbreakpointmain.cssuses, capped cells switch fromoverflow-wrap: anywheretobreak-word. The keywords differ in exactly the property that matters:anywherelets a cell's min-content width collapse to one glyph,break-wordfloors it at the longest unbreakable token. Measured after: 13 chars/line over 4 lines, 10 over 21, no horizontal scroll. Desktop is unchanged (the rule is inside the media query).white-space: nowrap(now a comment in the CSS):.table-cappedhits every 3+ column table, i.e. theField | Type | Default | Descriptionshape whose Description holds prose — 25% of capped cells in kgateway-oss exceed 60 characters, the longest ~2750. Forcing those onto one line measured a 5.7x-viewport horizontal scroll on the fixture and 33x against real agentgateway airgap/CRD tables. Prose has to keep wrapping; only the collapse floor needed fixing.us-docker.pkg.dev/…tokens, folding one character per line on a phone today. Verified withhugo160OSS + enterprise builds;tests/table-display.spec.tsnow bounds the behavior from both sides at 375px — an unbreakable-token block asserting ≥4 chars/line, and a prose block (backed by a new fixture table with ~200-char sentences) asserting the cell isn'tnowrap, wraps, clears the same floor, and keeps the table under 3x the viewport. Line counts come from real line boxes (Range client rects), since computedline-heighton these cells isnormal. The pair fails onanywhereand onnowrap. Takes effect on a pin bump.Fix — mobile chip-row edge fade uses a content mask so chip text no longer slides under the ‹ / › arrows (
assets/css/docs-theme-extras.css,assets/js/mobile-nav.js)linear-gradientbackground behind the arrows, so the gradient sat behind the text and a chip still slid visibly under the arrow — and the effect needed a hand-maintained dark-mode variant to match the panel color.mask-imageon the scroll track fades the content itself toward whichever edge can still scroll, so chip text dissolves before reaching the arrow and no dark-mode color variant is needed.mobile-nav.jstoggles.scroll-fade-start/.scroll-fade-endper edge and clears them at rest, so the first/last chip isn't faded where there's no arrow; the fade widths default to0.hugo160OSS + enterprise builds: the built stylesheet carries the[data-scroll-track]mask rule and the two fade widths, and both solid-gradient rules (plus their.darkvariants) are gone. Test gap: the per-edge toggle is verified by inspection, not by an assertion — the code runs on the path the AJAX-swap test exercises, but the fade itself is unasserted. Takes effect on a pin bump.Fix — paint markdown links in the version banner so they read as links (
assets/css/docs-theme-extras.css,hugo-*.toml,tests/version-banner-link.spec.ts)[[params.versions]].bannerrendered as a real<a href>but in the banner's own body color with no underline and no weight change — clickable but unfindable, which reads to an author as "the banner link is broken." Link generation was never the problem: the banner renders outside.content, and the theme's only anchor rules are scoped.content a, while Hextra's Tailwind preflight resetsato inherit color and text-decoration. The anchor therefore inherited--tw-prose-bodyfrom.version-banner..version-banner arules take color from the brand's runtime--theme-primary(both brand layers redefine it under.dark, so one rule covers both brands and both schemes) plusfont-weight: 500and an unconditional underline, since a color shift alone is weak against the banner's tinted fill.:hovermoves to--theme-primary-hoverand drops the underline, with a:where(.dark, .dark *)override holding the resting color in dark mode (--theme-primary-hoveris defined only in the light:root, and reusing it would darken the link toward the banner background).mainbanner was link-free, so no build in the harness had ever rendered a banner anchor. All four fixture configs now carry an internal and an external markdown link there, which also exercisesrender-link.html'srelURLandtarget="_blank"branches inside the banner..version-banner arule, so it renders in gray body text. Verified withhugo160OSS + enterprise builds and a newtests/version-banner-link.spec.ts, which auto-discovers a configured page whose banner holds an anchor and asserts, in both light and dark mode, that every anchor has a resolvablehref, differs from the banner body color or is underlined, and clears 3:1 against the banner fill (compositing the translucent dark-mode background over its opaque ancestor). Confirmed failing without the CSS and passing with it; fullmake test-allgreen on both brands (1648 passed, 14 skipped each). Because the spec keys off the consumer's own config, it carries signal against consumer builds too. Takes effect on a pin bump.--theme-primary(#158bc2) clears only ~3.6:1 against the light banner fill, under WCAG AA's 4.5:1 for normal text — which is why the spec's floor is 3:1. That is the brand's existing.content acolor, so darkening it is a brand-layer decision, not a banner fix.Fix —
link-hextrastops manglingreference/api-*sibling sections, and routes the agentgateway CEL reference to the enterprise page (layouts/_shortcodes/link-hextra.html,fixture/assets/conrefs/test/everything.md,tests/link-hextra-apiref.spec.ts)in $path "reference/api", a substring that also matches the sibling sectionreference/api-kubespec, so a shared-source link to/reference/api-kubespec/policies/#…was rewritten to/reference/api/api-kubespec/policies/— a path that exists on no build. Live on /agentgateway/2026.7.1/security/backend-authn-cross-app-access/: thecrossAppAccessfield link points at/agentgateway/2026.7.1/reference/api/api-kubespec/policies/. (2) agentgateway OSS splits its CEL reference into/reference/cel/variables/and/reference/cel/yaml-and-examples/; the enterprise docs serve one/reference/cel/page carrying the same anchors, so every rebased OSS page linking the CEL functions list 404s — 8 of them acrosslatest,2026.7.1, and2.3.x, e.g. the "CEL functions" link on /agentgateway/2026.7.1/traffic-management/transformations/rewrite/, which should land on /agentgateway/2026.7.1/reference/cel/#functions-policy-all.reference/api-joins the$alreadyRoutedlist, so anyreference/api-*sibling passes through untouched instead of being treated as the single-page reference. A new rule collapsesreference/cel/(variables|yaml-and-examples)/toreference/cel/, gated on the rebase-injectedproduct == "agentgateway"— the same product-only signal the existing agentgateway branch uses, so the OSS site (which sets no product) keeps its own paths and no assumption is made about the OSS site config. Anchors ride along unchanged, which is the point: the enterprise page publishes the same#functions-policy-allIDs.hugo160on both fixture brands.tests/link-hextra-apiref.spec.tsgrows five markers (theapi-kubespecsibling, plus the CEL variables/yaml/already-collapsed/no-signal cases): 9/9 on OSS and enterprise. Both guards were break-tested — dropping thereference/api-term and disabling the CEL rule fails exactly the three new assertions and leaves the six pre-existing ones green. Takes effect in the docs hub when it bumps its extras pin (currentlyv0.1.25-beta.2, no local replace).CI — parallelize the two brand layers and keep a single required status check (
.github/workflows/test.yml,playwright.config.ts)make test-allchainsbuild-oss+test-oss+build-enterprise+test-enterprisein one job, so every push paid ~2x the single-brand cost back-to-back; and a stalledsudo apt-getduring the browser install could hang to the 15-minute job timeout while a stale run lingered on the same branch. The brands are independent (separate builds,publishDir,DOCS_TEST_CONFIG), so nothing required a shared runner.matrix.brand: [oss, enterprise]withfail-fast: false;PW_WORKERSis tunable and set to4in CI; aconcurrencygroup keyed ongithub.refsupersedes in-flight PR runs; the browser-install steps carrytimeout-minutes: 5so a hungapt-getfails fast and re-runnable; and failure artifacts are per-brand so the jobs don't collide. Nothing here touches the published Hugo module.PW_WORKERSto 6.ubuntu-latestis a 4-core runner for public repos, and firefox is the engine that shows oversubscription first — locally at high parallelism it produced threepage.gototimeouts plus across-browser.spec.tsflake, all 11/11 at--workers=1. Making main the only home of firefox coverage and running it oversubscribed would mean engine regressions surface post-merge, in the runs most likely to be waved off as flake. SoPW_BROWSERSis the full set on every event,PW_WORKERSis4, andCROSS_BROWSER=chromiumsurvives only as a local escape hatch CI never sets. The cache key drops itsPW_BROWSER_TAGcomponent, which existed only to keep a chromium-only PR cache from being restored by an all-engines main run.cancel-in-progressis now PR-only. Cancellation is keyed on the branch, not the commit, so on main two quick merges would cancel the first one's run and leave a merged commit with no completed sweep.test-allaggregation job keeps branch protection stable. The matrix reports asbrand (oss)/brand (enterprise), so requiring those directly would mean re-pointing branch protection whenever the matrix changes shape. The matrix job is renamedbrand, and a dependent job (needs: [brand],if: always(), assertingneeds.brand.result == 'success') re-exposes the singletest-allcontext the rule already requires —cancelledandskippedfail the assertion too. No branch-protection edit is needed.hugo160: the full suite passes on both fixtures with all three engines,CROSS_BROWSER=chromiumdrops firefox + webkit, and the workflow resolves to exactly two jobs. (Moved here from v0.1.23 — the code landed after that tag was cut.)Fixture — give the
link-hextrareference/api markers real link targets (fixture/content/en/test/{main,v1,v2}/reference/)/test/v2/reference/api/…#TypeApaths the fixture never published, so every fixture build shipped seven broken links, reported against the docs hub as solo-io/docs#3349.tests/link-hextra-apiref.spec.tsasserts the emittedhrefstrings, so the markers were right — nothing had created the pages.reference/api/subtree with the three routed targets (single-page index, thekgatewayenterprise subpage, theapiagentgateway subpage). They exist in all three versions, not justv2, because the Rebased pages run theversion="v2"links through the two-pass remap onto their own version — which is why the report listedv1andmainpaths too. Anchors are explicit{#TypeA}heading attributes, not Goldmark slugs: generated API references emit CamelCase type IDs and the inbound links use that case, so## TypeAalone would publishid="typea"and lychee'sinclude_fragmentspass would flag it. Each page carriesbuild: {list: never, render: always}so the subtree renders as link targets without entering the sidebar or the auto-card counts thatauto-cards.spec.tsandsidebar-rail.spec.tspin.testproduct, which is excluded fromfirebase-hosting-merge.yml, so no pin bump is involved. Verified withhugo160on both brands: all nine pages build withid="TypeA"intact, per-version auto-card counts unchanged, nowarnMissingDescriptionwarnings,static+contentprojects pass (1469 passed). Re-ranlychee 0.24.2with the CI action's exact flags: all seven errors gone with zero fragment warnings, and a deliberately bogus#TypeZZZprobe still fails, so the anchors are genuinely checked.