Skip to content

[3.x] Support pages with more output file extensions (epic)#2524

Draft
emmadesilva wants to merge 97 commits into
masterfrom
v3/non-html-pages
Draft

[3.x] Support pages with more output file extensions (epic)#2524
emmadesilva wants to merge 97 commits into
masterfrom
v3/non-html-pages

Conversation

emmadesilva and others added 8 commits July 13, 2026 05:05
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a static $outputFileExtension property (default '.html') to HydePage,
used by outputPath() instead of the hardcoded HTML suffix, along with an
outputFileExtension() accessor matching the existing static accessors.

InMemoryPage identifiers can now declare a '.json', '.txt', or '.xml'
extension to compile to that path as-is, so
InMemoryPage::make('robots.txt', contents: ...) outputs '_site/robots.txt'.

This formalizes the route key convention already used by the documentation
search index: only the HTML extension is implicit in route keys, and pages
compiled to non-HTML files keep their extension in the route key. The
DocumentationSearchIndex getOutputPath() override is now redundant and removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds testOutputFileExtension to the BaseHydePageUnitTest contract with
implementations for all page classes, HydePage baseline and child class
override tests, and InMemoryPage tests covering identifier-declared
extensions including edge cases like dotted identifiers and unsupported
extensions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Covers the user path of registering pages like robots.txt in a kernel
booting callback and having them compiled to their declared output paths
through the standard build command, including route registration, nested
paths, and view-based compilation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completes the route key convention for page classes declaring a non-HTML
output file extension: RouteKey::fromPage() now appends the declared
extension to the key (unless the identifier already ends with it), so the
route key always equals the output path with only the HTML extension
implicit. Without this, a class declaring '.txt' would get the route key
'robots' but the output path 'robots.txt', breaking route resolution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records the resolved design decisions (identifier allowlist chosen for
InMemoryPage, route key extension handling in RouteKey::fromPage, no
setter, redirects intentionally not special-cased) and rewords the
breaking-change note to reflect that double-extension output was never
practically relied upon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@emmadesilva emmadesilva changed the title [3.x] Non HTML pages (epic) [3.x] Support pages with more output file extensions (epic) Jul 13, 2026
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (65391c3) to head (62feb3c).
⚠️ Report is 6 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##              master     #2524    +/-   ##
============================================
  Coverage     100.00%   100.00%            
- Complexity      1720      1791    +71     
============================================
  Files            175       179     +4     
  Lines           4308      4469   +161     
============================================
+ Hits            4308      4469   +161     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

emmadesilva and others added 20 commits July 13, 2026 14:15
Renames HydePage::$fileExtension to $sourceExtension, with the
fileExtension() and setFileExtension() methods becoming
sourceExtension() and setSourceExtension(). The old name was
underspecified: it actually meant the source file extension, which
becomes ambiguous now that pages also declare an output extension.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pairs $outputExtension with $sourceExtension so the two declarations
read as the symmetrical concepts they are, and documents the accessor
by its purpose (values like `.html` or `.txt`) rather than leading
with the serialization detail of the leading dot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ternary compressed the domain distinction (only the HTML extension
is implicit in route keys) to the point of needing a narrating comment.
The expanded conditional makes the rule visible on its own; the broader
route-key rule is documented on the RouteKey class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renames InMemoryPage::identifierDeclaresOutputExtension() to
identifierHasExplicitOutputExtension() and names the allowlist as the
EXPLICIT_OUTPUT_EXTENSIONS constant, so subclasses can customize the
recognized extensions by overriding data instead of replacing the
algorithm. The docblocks now state the observable behavior (which
extensions are recognized and what happens) instead of the vague
"supported".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The helper returned either an extension or an empty-string sentinel,
forcing the caller to concatenate an opaque fragment. Stating the whole
route-key construction in one place makes the "only HTML is implicit"
rule readable without control-flow-by-empty-string.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the breaking-change release note and upgrade step for the
$fileExtension to $sourceExtension rename, and starts an upgrade
script rules section in the planning document so the mechanical
migrations the release-time upgrade script must perform are collected
as we make them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The EXPLICIT_OUTPUT_EXTENSIONS constant is the advertised extension
point for subclasses, so cover that overriding it both recognizes the
custom extension and replaces the default list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Page classes can now compile to non-HTML files, so the accessor should
not describe the output as HTML.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The renamed methods are public and non-final, so a subclass override of
fileExtension() or setFileExtension() would silently stop being called
once the framework calls sourceExtension(). The upgrade script rule and
the upgrade guide now include renaming such declarations. Also moves
the upgrade script rules to their own section at the end of the
planning document instead of sitting under the empty v2-branch heading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Appending to the identifier before joining the base route key produced
keys like feed/.xml instead of feed.xml for non-HTML pages with an
empty identifier and an output directory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
outputExtension() returned the raw static property, so a typo like
'txt' (missing the dot) silently produced output paths like
documenttxt. The accessor now throws an InvalidArgumentException for
values that do not start with a dot or that contain path separators,
matching the normalization the source extension setter already applies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A page class still declaring $fileExtension (or overriding the renamed
methods) is not discoverable under the new API, so a build would
succeed while silently omitting the entire page type — including
unmodified vendor packages the upgrade script never touches. Discovery
now throws an actionable exception naming the class and the rename.
This is a temporary upgrade aid that can be removed in a future release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A redirect declared for a path like legacy.json appears valid and
produces a file, but the meta refresh markup cannot redirect when the
file is served as non-HTML content. The configuration now fails fast
with an InvalidConfigurationException instead of silently emitting a
broken page. Revises the earlier document-only decision in the epic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ends the coverage gap where only InMemoryPage was exercised through the
full build: a custom page class with a .md source extension and a .txt
output extension is now tested through source discovery, route
registration, the build command, and the output file name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
[3.x] Support pages with more output file extensions (Add a robots.txt generator)
@emmadesilva emmadesilva temporarily deployed to pr-documentation-2524 July 13, 2026 21:56 — with GitHub Actions Inactive
emmadesilva and others added 16 commits July 13, 2026 23:59
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… default

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e method

Removes HydePage::showInLlmsTxt() and its test contract entry. The generator now
reads `llms` front matter defaulting to the page's sitemap inclusion, which already
excludes generated non-HTML pages and redirects, keeping per-page control without
adding public API to every page class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The page types Hyde ships are known to the framework, so grouping them needed no
user input to be correct. Dropping the config removes its validation, exception
path, and drift guard, and leaves the llms config at two keys. Custom sections
remain available by overriding the generator and rebinding it in the container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drops the `llms` front matter key introduced alongside the generator. Leaving a page
out of llms.txt does not stop AI services from reading it, as crawler access is
governed by robots.txt, so the key could only ever mean "curate my index" — which is
what `sitemap: false` already means. Front matter is public API we support for the
life of the major version, and reintroducing the key later stays additive.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A page titled "Arrays [Advanced]" previously emitted a malformed Markdown link.
Also documents and tests the ordering contract: sections in declaration order, pages
in route order, which keeps numerically prefixed docs in their reading order. Makes
the section map a method so generator overrides can compute it at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pins the documented answer to the one case the sitemap-derived rule cannot express:
a page kept out of the sitemap for SEO reasons that should still reach an agent index.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
[3.x] Support pages with more output file extensions (Add a llms.txt generator)
@emmadesilva emmadesilva temporarily deployed to pr-documentation-2524 July 14, 2026 00:15 — with GitHub Actions Inactive
@emmadesilva emmadesilva temporarily deployed to pr-documentation-2524 July 14, 2026 01:27 — with GitHub Actions Inactive
@emmadesilva emmadesilva temporarily deployed to pr-documentation-2524 July 14, 2026 01:27 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant