Hub pages 1/4: hub page layout and the {hero} directive - #3825
Draft
florent-leborgne wants to merge 9 commits into
Draft
Hub pages 1/4: hub page layout and the {hero} directive#3825florent-leborgne wants to merge 9 commits into
florent-leborgne wants to merge 9 commits into
Conversation
This was referenced Aug 10, 2026
florent-leborgne
force-pushed
the
feature/hub-layout-hero
branch
from
August 11, 2026 07:56
2e48305 to
fc025c3
Compare
florent-leborgne
force-pushed
the
feature/hub-layout-hero
branch
from
August 11, 2026 13:29
dea613c to
a2fac91
Compare
florent-leborgne
force-pushed
the
feature/hub-layout-hero
branch
from
August 11, 2026 15:43
a2fac91 to
861e602
Compare
florent-leborgne
force-pushed
the
feature/hub-layout-hero
branch
from
August 11, 2026 16:50
861e602 to
f7ed067
Compare
theletterf
reviewed
Aug 12, 2026
theletterf
left a comment
Member
There was a problem hiding this comment.
Nice! Two Qs:
- How does a hub page render without a hero directive? Should it trigger an error if empty?
- Related to the above, do we want to keep hero directives independent and available to all pages? Shouldn't the hero block, if mandatory for all hub pages, be part of the layout and not a separate directive?
Member
|
I think we can roll with this one! I would just think of a warning in case a layout is missing what we consider mandatory directives (like hero). |
florent-leborgne
added a commit
that referenced
this pull request
Aug 12, 2026
Martijn's review on #3825 and theletterf's questions. - The hero no longer adds its own horizontal padding. The content column supplies the gutter, so the hero aligned with the sections below it only above the shared max-width. Below it the hero sat 24px further in. - Hero type moves onto the site's scale. The root font size is 14px, so values written as 16px-root arithmetic rendered around 12% small. The description in particular used a size found nowhere else on the site. - The three hero actions carry equal weight and take the secondary button treatment from the {button} directive, rather than a bespoke hero button. The option names set order, not weight. - A page with `layout: hub` and no {hero} now fails the build. The layout removes the page H1, so without a hero the page has no title at all and falls back to its file path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 12, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mpdreamz
approved these changes
Aug 13, 2026
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the first two pieces of the hub page mechanism, rebuilt on the current
navigation implementation. The prototype branch is merged with nav-v2, which is
out of scope, so nothing from nav-v2 ports.
Adds `layout: hub`, which drops the right-rail table of contents and the
previous/next navigation, keeps the left sidebar, and gives the body the full
content column. Page-title detection gains two fallbacks, so a page composed
only of directives still resolves a title: an H1 nested in a directive, then the
first {hero} directive's :title: option.
Adds {hero}, the identity band. It carries a product icon, the page title, a
description, and up to three optional actions.
Promotes the prototype's hub-only link validator to a shared
DirectiveLinkValidator. Directive links never pass through Markdig's inline link
parser, so every directive that takes a URL had invented its own handling.
{page-card} moves onto the shared class and its hand-rolled copy goes. It keeps
its previous contract: relative links resolve against the source file, and no
file-existence check runs, because page-card links can target generated pages
that have no markdown file on disk.
Guards DirectiveBlockParser.TryContinue so an ancestor directive stops consuming
option lines once it has opened a nested directive child. Without the guard the
ancestor swallows every descendant's options and the last one wins. This affects
every nested directive, so it lands with regression tests over tab sets,
steppers, and dropdowns.
Adds LLM markdown and plain-text renderers for {hero}. A hub page has no body
prose, so without them it exports as an empty shell. The plain-text renderer
feeds the search index and emits the hero title and description only. Hub pages
answer generic "<product> docs" queries. Indexing section and card titles would
let a hub compete with the pages it links to on specific queries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three defects, all introduced in this branch rather than carried from the
prototype.
Suppress the page-level H1 and the applies-to badge on a hub page. The prototype
guards both in Index.cshtml and that guard was missed, so every hub page rendered
the page title twice: once from Index.cshtml, once from {hero}. This also
delivers the applies-to suppression that #1386 records but the code did not do.
Drop the markdown-content class from the hub article. The class was added to
reach the JS hooks, but those select on the markdown-content id, not the class.
typography.css is unlayered and hub.css sits in @layer components, so unlayered
rules win regardless of specificity. The class made typography.css restyle the
hero eyebrow as a blue underlined link and skew the icon alignment against the
heading. The id stays, so hljs, smooth-scroll and toc-nav keep working.
Give hero actions the same link treatment as inline links. An external action now
opens in a new tab with rel="noopener noreferrer" and no preload, and an anchor
action no longer preloads. Only an internal link preloads.
Fix an empty href for "/index.md". Stripping the whole "/index.md" segment left
an empty string rather than the site root.
The example page gains three actions, one internal and two external, to
demonstrate both forms.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mark sat about 9px right of the eyebrow and the description below it. Two insets stacked. Each icon's viewBox carried transparent padding around the glyph, which cost 5px at the rendered size. The 48px chip then centred the 40px SVG inside itself, which cost another 4px. Crops every icon's viewBox to its glyph, so no transparent padding is left to absorb, and lets the box hug the mark instead of forcing it into a fixed square. Width now follows from the glyph's own aspect ratio. The letter fallback keeps the chip. It has no glyph to align, and the chip's own edge already lines up with the text column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tabs The example page was an Elasticsearch hub whose links had all been repointed at docs-builder pages, leaving an Elasticsearch name, icon, hero and headings on top of docs-builder content. It is now a docs-builder hub throughout, renamed to docs/examples/products/docs-builder.md. Its hero demonstrates all three link forms: a site-absolute path, a docs-content cross-link, and an in-page anchor. The unknown `docs-builder` icon key also exercises the single-letter fallback. Fixes a defect the cross-link uncovered. A cross-link resolves to a full URL, so the hero treated it as external and opened it in a new tab. Inline links make the opposite call, and deliberately so: a cross-link still points at documentation this site serves. DirectiveLinkValidator now records the URLs it resolved from a cross-link scheme, and the view model consults that rather than guessing from the scheme. Adds doesNotContainHtml to the authoring assertions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Martijn's review on #3825 and theletterf's questions. - The hero no longer adds its own horizontal padding. The content column supplies the gutter, so the hero aligned with the sections below it only above the shared max-width. Below it the hero sat 24px further in. - Hero type moves onto the site's scale. The root font size is 14px, so values written as 16px-root arithmetic rendered around 12% small. The description in particular used a size found nowhere else on the site. - The three hero actions carry equal weight and take the secondary button treatment from the {button} directive, rather than a bespoke hero button. The option names set order, not weight. - A page with `layout: hub` and no {hero} now fails the build. The layout removes the page H1, so without a hero the page has no title at all and falls back to its file path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Martijn's point about tiny fonts applies to every piece of hub text, not only the ones in his screenshot. The eyebrow is a reading affordance, so it takes the body size. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The site puts an arrow on two kinds of link: one that sends the reader
onward out of a section, such as "View all reference docs" on the landing
page, and a row that is itself the target, such as {page-card} or the
previous and next footer. It marks no ordinary link and no button.
The hero followed neither. Its actions are buttons, so they carried an
arrow the site gives no button, and only when the target was an in-page
anchor. Both are gone.
The eyebrow keeps its arrow, because it sends the reader onward to the docs
home. It now uses the site's glyph and hover slide through one shared
`.hub-arrow` class, rather than a second arrow drawn by hand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#3853 added `:type: neutral` to the {button} directive, a monochrome outline for clusters where blue competes with the primary action. That is exactly what the hero needs: three actions of equal rank, none of which should claim to be the call to action. #3853 also flagged that this stack used border colours failing the 3:1 non-text contrast ratio. The eyebrow pill moves from grey-20, which reads at 1.32:1, to grey-70 at 3.31:1. The border is what identifies that control, so it has to clear the threshold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hub heroes fall back to initials when a product key is missing from the inline SVG map. Logstash hubs need the product mark. Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Martijn Laarman <Mpdreamz@gmail.com>
florent-leborgne
force-pushed
the
feature/hub-layout-hero
branch
from
August 13, 2026 15:54
d0395b2 to
87136e7
Compare
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rules apply here. - Get started type moves onto the site's scale. The values it carried were 16px-root arithmetic against a 14px root, so every one rendered small. - Step numbers move from grey-70 to ink-light. grey-70 reads at 3.31:1 on white, short of the 4.5:1 minimum, and ink-light carries the same quiet weight at 7.03:1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
florent-leborgne
added a commit
that referenced
this pull request
Aug 13, 2026
Martijn's review on #3825 covered the whole page, so the same rule applies here. What's new type moves onto the site's scale. Every raw font size in hub.css is now a scale token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Part 1 of 4. Implements elastic/docs-content-internal#1386 (
layout: hub) and #1380 ({hero}), against the promotion bar in #1379.Demo: https://docs-v3-preview.elastic.dev/elastic/docs-builder/pull/3825/examples/products/docs-builder
Rebuilt on the current navigation implementation. The prototype branch is merged with
nav-v2, which is out of scope, so nothing fromnav-v2ports.What this adds
MarkdownPageLayout.Huband theRenderHubbranch. It drops the right-rail table of contents and the previous/next navigation, keeps the left sidebar, and gives the body the full content column.{hero}'s:title:. A hub page has no authored H1.{hero}: product icon, page title, description, and up to three optional actions.layout: huband no{hero}fails the build. The layout removes the page H1, so the page would otherwise have no title.docs/syntax/hub-pages.md,docs/syntax/hero.md, an example hub atdocs/examples/products/docs-builder.md, and F# authoring tests.Visual decisions
{button}directive's neutral style from feat(button): add neutral variant to the button directive #3853. All three carry equal weight, so the option names set order, not prominence. A blue variant would make each action claim to be the call to action.grey-70. feat(button): add neutral variant to the button directive #3853 flagged that this stack used border colours failing the 3:1 non-text contrast ratio, and the border is what identifies that control.Implementation choices
DirectiveLinkValidator. Directive links never reach Markdig's inline link parser, so every directive taking a URL had invented its own handling. This promotes the prototype's hub-only validator to a shared one and moves{page-card}onto it, deleting its hand-rolled copy.{page-card}keeps its contract: relative links resolve against the source file, and no file-existence check runs, because its links can target generated pages such as the CLI reference.Parser guard.
DirectiveBlockParser.TryContinuestops an ancestor directive consuming an option line once it has opened a nested directive child. Without it the ancestor takes every descendant's options and the last one wins.The collision only fires when a parent and child read the same option name. Only the hub pair overlaps:
{explore}and{card-group}both use:id:and:title:. Existing pairs split cleanly, so they were latently exposed but never broken.{tab-set}group{tab-item}sync,selected{applies-switch}group{applies-item}sync{stepper}{step}anchor{dropdown}open{note}nametests/authoring/Blocks/NestedDirectiveOptions.fspins that each option still reaches the block that declared it, for all four pairs. Those tests pass with the guard removed, so they are guard-rails against breaking option routing rather than regression tests for the bug itself. The{explore}tests in #3826 are what catch the bug.The guard changes exactly one pattern: an option line appearing after a nested child has opened. A scan of every markdown file in docs-builder and docs-content found zero occurrences.
Cross-links are not external. A cross-link resolves to a full URL but still points at documentation this site serves, so it does not open in a new tab. Inline links make the same distinction.
Machine-readable output.
{hero}gets an LLM renderer and a plain-text renderer. The search body takes the hero title and description only. Indexing section and card titles would let a hub outrank the pages it links to.Article id, not class. The article keeps
id="markdown-content", which the copy button, syntax highlighter and smooth scroll select on. It does not take the class:typography.cssis unlayered andhub.csssits in@layer components, so the class would override every hub rule.Two prototype details do not port. The empty
#toc-navslot is dropped, becausemainreplacedhx-select-oobwith a whole-body swap. Hero links usepreloadrather thanhx-boost="false", matchingPageCardView.Known gaps
VersionDropdownrenders from the table-of-contents rail this layout removes. The designs place it in the top bar, which belongs to the navigation work in elastic/docs-content-internal#1589.:description:uses the default Markdig pipeline, because it is a directive option rather than a body block. Substitutions, roles and link validation do not apply inside it. Documented on the syntax page.Testing
./build.sh unit-testpasses.dotnet formatandnpm run fmt:checkare clean. A full docs build reports 0 errors and 0 warnings.Adds
containsRawHtmlanddoesNotContainHtmlto the authoring assertions. The pretty-printed comparison only matches a childless element or a snapshot taken from the outermost element of the directive output, so asserting an attribute on a container needs the raw form.Screenshots to add or update
None attached. Check the rendered hero on the preview.