Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
93c21d0
Add design document for versioned documentation pages
emmadesilva Jul 11, 2026
21e9ac0
Add documentation version registry for versioned documentation pages
emmadesilva Jul 11, 2026
378ed42
Make documentation pages version-aware
emmadesilva Jul 11, 2026
7bce1ae
Add version-aware documentation sidebars and navigation
emmadesilva Jul 11, 2026
331f679
Add per-version documentation search indexes and search pages
emmadesilva Jul 11, 2026
cf14aa2
Add documentation version switcher component
emmadesilva Jul 11, 2026
1ab4f1f
Redirect the documentation root to the default version index page
emmadesilva Jul 11, 2026
9c28e04
Document the versioned documentation pages feature
emmadesilva Jul 11, 2026
6b0c732
Update documentation-page-methods.md
emmadesilva Jul 11, 2026
2cb6ae2
Create feature tests
emmadesilva Jul 11, 2026
d556536
Add additional tests
emmadesilva Jul 11, 2026
67337c6
Remove unnecessary unslash
emmadesilva Jul 11, 2026
b0da7f4
Fix introduced bug
emmadesilva Jul 11, 2026
586c7f0
Update the realtime compiler to support dotted path segments
emmadesilva Jul 11, 2026
6857483
Cleanup code call
emmadesilva Jul 11, 2026
ec8694d
Restore the static 404 response for missing assets
emmadesilva Jul 11, 2026
964ecf6
Only add the documentation root redirect when it has a destination
emmadesilva Jul 11, 2026
8b9ebfc
Support excluding pages from search by their route keys
emmadesilva Jul 11, 2026
42d2ffc
Hide the version switcher for pages that are not in a version
emmadesilva Jul 11, 2026
adef501
Check for a matching page route before compiling the page
emmadesilva Jul 11, 2026
ec8e190
Document how unversioned documentation pages behave
emmadesilva Jul 11, 2026
6b9e8ff
Test the boundary between missing assets and missing pages
emmadesilva Jul 11, 2026
296f63e
Derive documentation version defaultness through the registry
emmadesilva Jul 11, 2026
d87e4fe
Give the documentation home route a single owner
emmadesilva Jul 11, 2026
51a1226
Require documentation pages to be versioned when versioning is enabled
emmadesilva Jul 11, 2026
6207346
Alias the default sidebar service to the default version's sidebar
emmadesilva Jul 11, 2026
660abb0
Update the versioned documentation docs for the revised design
emmadesilva Jul 11, 2026
a33348e
Warn about ignored unversioned documentation files
emmadesilva Jul 11, 2026
2064553
Remove DocumentationVersion::isDefault()
emmadesilva Jul 11, 2026
c101e51
Delete versioned-documentation-design.md
emmadesilva Jul 11, 2026
60ef181
Type hint documentation page instead of general page
emmadesilva Jul 11, 2026
6fa11ab
Don't show version switcher on the search pages
emmadesilva Jul 11, 2026
d3ca2bf
Revert "Don't show version switcher on the search pages"
emmadesilva Jul 11, 2026
fc862cd
Union type documentation search page
emmadesilva Jul 11, 2026
4b36400
Pure method should not boot the application
emmadesilva Jul 11, 2026
56bfe17
Fix so test sets up its needed data
emmadesilva Jul 11, 2026
1292548
Delete overengineered interface
emmadesilva Jul 11, 2026
10f68d8
Clean up code comments
emmadesilva Jul 11, 2026
64e5eec
Refactor to simplify internal code
emmadesilva Jul 11, 2026
725875a
Refactor to simplify the code
emmadesilva Jul 11, 2026
a9f01d8
Improve data state handling
emmadesilva Jul 11, 2026
7fd5031
Refactor and clean up the code
emmadesilva Jul 11, 2026
6fbb835
Remove unnecessary code comment
emmadesilva Jul 11, 2026
63ef13a
Split out complex test
emmadesilva Jul 11, 2026
7338e4e
Resolve the documentation version from the rendered page's route key
emmadesilva Jul 11, 2026
81b9ef3
Keep the main navigation exclusion config scoped to route keys
emmadesilva Jul 11, 2026
a6d0beb
Make the router's asset resolution state explicit
emmadesilva Jul 11, 2026
fdc209f
Clarify what the generator's version property selects
emmadesilva Jul 11, 2026
a93dc91
Trim comments that restate the code
emmadesilva Jul 11, 2026
cb2ce66
Fix version switcher's accessibility semantics
emmadesilva Jul 11, 2026
a270167
Build assets for production
emmadesilva Jul 11, 2026
e7b362b
Link to the pull request
emmadesilva Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions HYDEPHP_V3_PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ Having this document in code lets us know the devlopment state at any given poin

### New Features

- Added native support for versioned documentation pages. Register versions in the new `docs.versions` configuration option, and store the pages for each version in a matching subdirectory of the documentation source directory (like `_docs/1.x` and `_docs/2.x`). Each version is compiled to a matching subdirectory of the documentation output directory, and gets its own sidebar, search index, and search page. A version switcher dropdown is shown in the documentation sidebar, the main navigation links to the default version's index page, and a redirect page is generated at the documentation root pointing to the default version. Sidebar and search configuration entries (`docs.sidebar.order`, `docs.sidebar.labels`, `docs.sidebar.exclude`, and `docs.exclude_from_search`) match version-agnostic identifiers and route keys, so a single entry applies to the page in every version, while full versioned keys allow version-specific overrides. Enabling the feature is all or nothing: documentation source files stored outside the version directories are ignored, so pages that should live at the documentation root belong in the normal page source directory (like `_pages/docs/index.md`). Versioning is disabled by default, and single-version sites are unaffected. ([#2516](https://github.com/hydephp/develop/pull/2516))

### Feature Changes

- BladeDown (Blade in Markdown) is now enabled by default. Hyde sites generally treat project content as trusted and reviewed; sites that compile untrusted or unreviewed Markdown can disable it with `markdown.enable_blade`.

### Minor Changes and Cleanup

- The `Redirect` page class constructor now accepts an optional `$matter` parameter, used by the framework to hide the generated documentation root redirect from navigation menus. Existing usages are unaffected.

- Removed the legacy `checkForDeprecatedRunMixCommandUsage` check and the placeholder `--run-dev`/`--run-prod` options from the `build` command, which were kept in v2 only to surface a helpful error message. ([#2461](https://github.com/hydephp/develop/pull/2461))
- Removed the deprecated `hyde.server.dashboard` boolean config check from `DashboardController::enabled()`, which was kept in v2 for backwards compatibility but had since then been replaced with `hyde.server.dashboard.enabled`. ([#2461](https://github.com/hydephp/develop/pull/2462))
- Upgraded the bundled `vite` dependency from v7 to v8, and widened the `hyde-vite-plugin`'s `vite` peer dependency range from `>=6.3.5 <8.0.0` to `>=6.3.5 <9.0.0` so downstream projects can adopt Vite 8 without waiting for a new plugin major. The plugin's build config now targets Vite 8's Rolldown-based bundler (`rolldownOptions` instead of `rollupOptions`). ([#2414](https://github.com/hydephp/develop/pull/2414))
Expand Down
31 changes: 31 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,37 @@ StaticPageBuilder::handle(Pages::getPage('_posts/hello-world.md'));

Note that this only produces a correct `_site` when the page is self-contained. For anything that touches aggregate outputs, run `php hyde build` to rebuild the whole site instead.

## Optional: Adopt Versioned Documentation

HydePHP v3 adds native support for hosting multiple versions of your documentation side by side. This feature is entirely opt-in — if you do nothing, your documentation site works exactly as before.

To enable it:

1. Move your documentation source files into version subdirectories, for example `_docs/1.x/` and `_docs/2.x/`.
2. Register the versions in `config/docs.php`:

```php
// filepath: config/docs.php
'versions' => [
'1.x',
'2.x',
],
```

Each version is compiled to a matching subdirectory of the documentation output directory (`docs/1.x`, `docs/2.x`), with its own sidebar, search index, and search page. A version switcher is shown in the sidebar, and `docs/index.html` is generated as a redirect to the default version's index page (the last entry in the list, or set `docs.default_version` explicitly).

Versioning is all or nothing: once you register versions, every documentation page must live in a version directory. Make sure step 1 is complete, as any Markdown files left directly in `_docs` are ignored, and will no longer be compiled. Each ignored file is reported as a build warning, so if you miss one, `php hyde build` tells you which:

```
Ignoring unversioned documentation file "_docs/installation.md" as documentation versioning is enabled. Move it into a registered version directory to include it in the site.
```

If you want a page at the documentation root, create it in your normal page source directory instead, for example `_pages/docs/index.md`, which then replaces the generated redirect.

Your existing `docs.sidebar.order`, `docs.sidebar.labels`, `docs.sidebar.exclude`, and `docs.exclude_from_search` entries keep working without version prefixes — they apply to the matching page in every version. Prefix an entry with a version (like `2.x/readme` or `docs/2.x/readme`) to target a single version.

If you previously implemented multi-version documentation with custom page classes or extensions (like early versions of HydePHP.com did), you can now remove that custom code in favor of the `docs.versions` configuration, keeping your existing `_docs/<version>` directory layout as-is.

## Migration Checklist

Use this checklist to track your upgrade progress:
Expand Down
2 changes: 1 addition & 1 deletion _media/app.css

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions config/docs.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@

return [

/*
|--------------------------------------------------------------------------
| Documentation Versions
|--------------------------------------------------------------------------
|
| Each version maps `_docs/<version>` to `docs/<version>`.
| Leave this empty to disable versioning. When enabled, documentation
| files outside registered version directories are ignored.
|
| The default version defaults to the last entry in the list.
|
*/

'versions' => [
// '1.x',
// '2.x',
],

'default_version' => null,

/*
|--------------------------------------------------------------------------
| Sidebar Settings
Expand Down
22 changes: 17 additions & 5 deletions docs/_data/partials/hyde-pages-api/documentation-page-methods.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
<section id="documentation-page-methods">

<!-- Start generated docs for Hyde\Pages\DocumentationPage -->
<!-- Generated by HydePHP DocGen script at 2023-03-10 20:56:21 in 0.38ms -->
<!-- Generated by HydePHP DocGen script at 2026-07-11 18:04:37 in 0.43ms -->

#### `home()`

No description provided.
Get the route for the documentation index page, if it exists.

When documentation versioning is enabled, this is the generated redirect page at the documentation root.

```php
DocumentationPage::home(): Hyde\Support\Models\Route
```

#### `homeRouteName()`

No description provided.
Get the route key for the documentation index page, for example `docs/index`.

This is always the documentation root, regardless of whether documentation versioning is enabled.

```php
DocumentationPage::homeRouteName(): string
```

#### `getDocumentationVersion()`

Get the documentation version this page belongs to, or null if documentation versioning is disabled.

```php
$page->getDocumentationVersion(): Hyde\Framework\Features\Documentation\Versioning\DocumentationVersion
```

#### `getOnlineSourcePath()`

No description provided.
Expand All @@ -31,7 +43,7 @@ $page->getOnlineSourcePath(): string|false

Get the route key for the page.

If flattened outputs are enabled, this will use the identifier basename so nested pages are flattened.
If flattened outputs are enabled, this will use the identifier basename so nested pages are flattened. Pages belonging to a documentation version keep the version prefix, so only the structure within the version is flattened.

```php
$page->getRouteKey(): string
Expand All @@ -41,7 +53,7 @@ $page->getRouteKey(): string

Get the path where the compiled page will be saved.

If flattened outputs are enabled, this will use the identifier basename so nested pages are flattened.
If flattened outputs are enabled, this will use the identifier basename so nested pages are flattened. Pages belonging to a documentation version keep the version prefix, so only the structure within the version is flattened.

```php
$page->getOutputPath(): string
Expand Down
20 changes: 20 additions & 0 deletions packages/framework/config/docs.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@

return [

/*
|--------------------------------------------------------------------------
| Documentation Versions
|--------------------------------------------------------------------------
|
| Each version maps `_docs/<version>` to `docs/<version>`.
| Leave this empty to disable versioning. When enabled, documentation
| files outside registered version directories are ignored.
|
| The default version defaults to the last entry in the list.
|
*/

'versions' => [
// '1.x',
// '2.x',
],

'default_version' => null,

/*
|--------------------------------------------------------------------------
| Sidebar Settings
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
@props(['modal' => true])
@props([
'modal' => true,
'searchIndexPath' => \Hyde\Framework\Features\Documentation\DocumentationSearchIndex::routeKey(
\Hyde\Framework\Features\Documentation\Versioning\DocumentationVersions::current()
),
])

<div id="hyde-search" x-data="hydeSearch">
<template id="search-highlight-template">
Expand Down Expand Up @@ -38,7 +43,7 @@

document.addEventListener('alpine:init', () => {
Alpine.data('hydeSearch', () =>
initHydeSearch('{{ Hyde::relativeLink(\Hyde\Framework\Features\Documentation\DocumentationSearchIndex::outputPath()) }}')
initHydeSearch('{{ Hyde::relativeLink($searchIndexPath) }}')
);
});
</script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<nav id="mobile-navigation" class="bg-white dark:bg-gray-800 md:hidden flex justify-between w-full h-16 z-40 fixed left-0 top-0 p-4 leading-8 shadow-lg print:hidden">
<strong class="px-2 mr-auto">
@if(DocumentationPage::home() !== null)
<a href="{{ DocumentationPage::home() }}">
@php
$homeRoute = \Hyde\Framework\Features\Navigation\DocumentationSidebar::get()->getHomeRoute();
@endphp
@if($homeRoute !== null)
<a href="{{ $homeRoute }}">
{{ config('docs.sidebar.header', 'Documentation') }}
</a>
@else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div id="sidebar-brand" class="flex items-center justify-between h-16 py-4 px-2">
<strong class="px-2">
@if(DocumentationPage::home())
<a href="{{ DocumentationPage::home() }}">
@if($sidebar->getHomeRoute())
<a href="{{ $sidebar->getHomeRoute() }}">
{{ $sidebar->getHeader() }}
</a>
@else
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
@php
$sidebar = app('navigation.sidebar');
$sidebar = \Hyde\Framework\Features\Navigation\DocumentationSidebar::get();
@endphp

<aside id="sidebar" x-cloak :class="sidebarOpen ? 'visible left-0' : 'invisible -left-64 md:visible md:left-0'" class="bg-gray-100 dark:bg-gray-800 dark:text-gray-200 h-screen w-64 fixed z-30 md:flex flex-col shadow-lg md:shadow-none transition-all duration-300">
<header id="sidebar-header" class="h-16">
@include('hyde::components.docs.sidebar-brand')
</header>
@include('hyde::components.docs.version-switcher')
<nav id="sidebar-navigation" class="p-2 overflow-y-auto border-y border-gray-300 dark:border-[#1b2533] h-full">
@include('hyde::components.docs.sidebar-items')
</nav>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@php
/** @var \Hyde\Framework\Features\Navigation\DocumentationSidebar $sidebar */
$switcherVersions = \Hyde\Framework\Features\Documentation\Versioning\DocumentationVersions::all();
$switcherCurrentPage = \Hyde\Support\Facades\Render::getPage();

$switcherCurrentVersion = $sidebar->version;
@endphp

@if($switcherCurrentVersion !== null && $switcherVersions->count() > 1)
<div id="docs-version-switcher" x-data="{ versionSwitcherOpen: false }" @click.outside="versionSwitcherOpen = false"
@keydown.escape.window="versionSwitcherOpen = false; $refs.versionSwitcherButton.focus()" class="relative px-4 pb-3">
<button id="docs-version-switcher-button" x-ref="versionSwitcherButton" @click="versionSwitcherOpen = ! versionSwitcherOpen" :aria-expanded="versionSwitcherOpen"
aria-controls="docs-version-switcher-list" aria-label="Switch documentation version"
class="w-full flex items-center justify-between rounded-sm text-sm leading-normal bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 py-1.5 px-3 transition-colors duration-150">
<span>Version {{ $switcherCurrentVersion->name }}</span>
<svg class="w-4 h-4 opacity-75 transition-transform duration-150" :class="versionSwitcherOpen ? 'rotate-180' : ''" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</button>
<ul id="docs-version-switcher-list" x-cloak x-show="versionSwitcherOpen" aria-labelledby="docs-version-switcher-button"
class="absolute left-4 right-4 z-40 mt-1 max-h-64 overflow-y-auto rounded-sm shadow-lg bg-white dark:bg-gray-700 py-1 text-sm">
@foreach($switcherVersions as $switcherVersion)
@php
$switcherRoute = $switcherCurrentPage !== null
? (\Hyde\Framework\Features\Documentation\Versioning\DocumentationVersions::getEquivalentRoute($switcherCurrentPage, $switcherVersion) ?? $switcherVersion->home())
: $switcherVersion->home();
@endphp
<li>
@if($switcherVersion->name === $switcherCurrentVersion->name)
<span aria-current="page" class="block py-1 px-3 font-medium opacity-75">{{ $switcherVersion->name }}</span>
@elseif($switcherRoute !== null)
<a href="{{ $switcherRoute }}" class="block py-1 px-3 hover:bg-gray-100 dark:hover:bg-gray-600">{{ $switcherVersion->name }}</a>
@else
<span aria-disabled="true" class="block py-1 px-3 opacity-50">{{ $switcherVersion->name }}</span>
@endif
</li>
@endforeach
</ul>
</div>
@endif
21 changes: 17 additions & 4 deletions packages/framework/src/Console/Commands/BuildSearchCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
use LaravelZero\Framework\Commands\Command;
use Hyde\Framework\Features\Documentation\DocumentationSearchPage;
use Hyde\Framework\Features\Documentation\DocumentationSearchIndex;
use Hyde\Framework\Features\Documentation\Versioning\DocumentationVersion;
use Hyde\Framework\Features\Documentation\Versioning\DocumentationVersions;

/**
* Run the build process for the documentation search index.
Expand All @@ -23,12 +25,23 @@ class BuildSearchCommand extends Command

public function handle(): int
{
StaticPageBuilder::handle(Pages::get('docs/search.json') ?? new DocumentationSearchIndex());

if (DocumentationSearchPage::enabled()) {
StaticPageBuilder::handle(Pages::get('docs/search') ?? new DocumentationSearchPage());
if (DocumentationVersions::enabled()) {
DocumentationVersions::all()->each(function (DocumentationVersion $version): void {
$this->build($version);
});
} else {
$this->build(null);
}

return Command::SUCCESS;
}

protected function build(?DocumentationVersion $version): void
{
StaticPageBuilder::handle(Pages::get(DocumentationSearchIndex::routeKey($version)) ?? new DocumentationSearchIndex($version));

if (DocumentationSearchPage::enabled($version)) {
StaticPageBuilder::handle(Pages::get(DocumentationSearchPage::routeKey($version)) ?? new DocumentationSearchPage($version));
}
}
}
Loading
Loading