[3.x] Support versioned documentation pages#2516
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2516 +/- ##
============================================
Coverage 100.00% 100.00%
- Complexity 1608 1676 +68
============================================
Files 168 170 +2
Lines 4039 4187 +148
============================================
+ Hits 4039 4187 +148 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This doubles as a handoff document tracking implementation progress for the native versioned documentation feature. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the DocumentationVersion value object, the DocumentationVersions registry reading the new docs.versions and docs.default_version config options, and the HasDocumentationVersion interface. Versioning is disabled by default when the versions list is empty. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documentation pages stored in registered version subdirectories now resolve their version, keep the version prefix when flattening output paths, and the home route helpers accept a version, defaulting to the default version when versioning is enabled. The version registry now also tolerates being called from configuration files while the configuration itself is being loaded, treating versioning as disabled at that point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each documentation version now gets its own sidebar, registered as
navigation.sidebar.{version} container singletons, with the plain
navigation.sidebar binding holding the default version's sidebar.
DocumentationSidebar::get() resolves the sidebar for the version of
the page currently being rendered.
Sidebar and navigation configuration entries (order, labels, exclude)
now also match version-agnostic identifiers and route keys, so a
single configuration entry applies to the page in every version,
while still allowing version-specific overrides via full keys.
Automatic sidebar grouping and index page titles skip the version
directory segment, and the main navigation menu links to the default
version's documentation index page.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When documentation versioning is enabled, each version now gets its own search index and search page (docs/1.x/search.json, docs/1.x/search), containing only the pages belonging to that version. The search component resolves the index for the version of the rendered page, and the build:search command builds all version shards. Version-agnostic docs.exclude_from_search entries apply to the page in every version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a version switcher dropdown to the documentation sidebar, shown when documentation versioning is enabled. It links to the equivalent page in each version when it exists, falling back to that version's index page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When documentation versioning is enabled, a redirect page is generated at the documentation root (docs/index) pointing to the default version's index page, so inbound links to the docs root always have a destination and no duplicate HTML is built for it. Creating your own page with the docs/index route key overrides the generated redirect. The Redirect page class now accepts optional front matter, which the generated redirect uses to hide itself from navigation menus. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds release notes, an upgrade guide section for adopting versioned documentation, and marks the design document as implemented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex <codex@openai.com>
8ec3c8f to
2cb6ae2
Compare
The real-time compiler dev server previously misclassified paths with dotted segments (like `/docs/1.x`) as static assets by blindly parsing the last segment as a file extension (e.g., `x`). This caused versioned documentation routes to bypass the PageRouter and incorrectly return a 404. This commit updates `Router::shouldProxy()` to stop guessing based on extensions. Instead, it delegates to `AssetFileLocator::find()` to check if the static asset actually exists on disk. If it doesn't, the request properly falls through to the PageRouter. Key implementation details: * Config Independence: By checking the disk directly, custom extensions added via the `hyde.media_extensions` config are automatically supported. This avoids the trap of using a hardcoded extension allowlist before the application has booted. * Performance Optimization: Added memoization (`resolveAssetPath()`) to cache the `AssetFileLocator::find()` result per request. This eliminates a pre-existing duplicate I/O check where both `shouldProxy()` and `proxyStatic()` were querying the disk, ensuring dev server response times remain negligible. * Test Updates: Updated `RealtimeCompilerTest` to reflect the new fallback behavior. Missing extension-like paths outside of `/media/` now accurately hit the `RouteNotFoundException` via the page router rather than returning a raw static 404.
The previous commit made the router fall through to the page router for any path with a dotted segment that isn't an existing file, so that versioned documentation routes like `/docs/1.x` resolve. But that also redefined every missing dotted resource as a missing page: a request for a nonexistent stylesheet, source map, or manifest would throw a RouteNotFoundException and render the pretty error page, instead of returning a clean 404 as it did before. Browsers request such files on their own, so this would also create exception noise in the dev server. We now still route dotted paths through the page router, but convert a route miss on an asset-like path back into the static 404 response. The shared `looksLikeAsset()` helper keeps the definition in one place, so that missing `.html` paths still throw, just like they did before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The generated `docs/index.html` redirect pointed to the default version's index page without checking that the page exists, so a site where the default version has no index page would get a redirect to a dead link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sidebar configuration options match pages by identifier or route key, in both their version-specific and version-agnostic forms, and the upgrade guide documents the search exclusions as working the same way. But the search index only compared identifiers, so a documented entry like `docs/2.x/readme` was silently ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The switcher took its current version from the sidebar, which falls back to the default version's sidebar when the rendered page has no version. Documentation pages stored outside the version directories were thus presented as belonging to the default version, and switching versions would take you to a version home page instead of an equivalent page. The switcher now takes its current version from the rendered page, and is hidden when that page does not belong to a version. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
b6bdd59 to
63ef13a
Compare
The version switcher passes whatever Render::getPage() returns into getEquivalentRoute(), so restricting it to DocumentationPage and DocumentationSearchPage raised a TypeError when a versioned page was overridden by a custom page, which the feature explicitly supports. Resolving the version from the route key instead of matching concrete page classes also lets the sidebar and version switcher support custom overrides and future version-aware page types. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralizing the configuration keys meant hyde.navigation.exclude also matched documentation identifiers and version-agnostic keys, which broadened its public contract beyond the route keys it accepted before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The memoized path used false, null, and string to mean three different things, and looksLikeAsset() only checked the extension, so both needed a comment to decode them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The upgrade guide already explains the versions configuration in full, and the removed comments restated their test or method names. Also document the front matter parameter the Redirect constructor added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The switcher declared role="listbox"/role="option" but its options were plain links with no listbox keyboard behavior implemented. Treat it as a disclosure containing a plain list of links instead: drop the listbox/option roles and aria-haspopup/aria-selected, mark the active version with aria-current="page", and connect the button to the list via aria-controls. Also close the dropdown on Escape and return focus to the trigger button, and cap the list height with a scrollbar so a long list of versions stays usable without overflowing the viewport.
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.
No description provided.