Skip to content

Add white-label branding support for isolated builds#3159

Merged
Mpdreamz merged 5 commits into
mainfrom
feature/white-label
May 6, 2026
Merged

Add white-label branding support for isolated builds#3159
Mpdreamz merged 5 commits into
mainfrom
feature/white-label

Conversation

@Mpdreamz
Copy link
Copy Markdown
Member

@Mpdreamz Mpdreamz commented Apr 20, 2026

Why

`elastic/docs-builder` and its GitHub Pages action are open source, but until now every isolated build output was visually tied to Elastic — the Elastic logo in the header, the Elastic footer with copyright and legal links, and hard-coded references to `elastic/` GitHub URLs throughout. This made it impractical for any non-Elastic OSS project to use docs-builder to publish their own documentation.

This PR adds a `branding` block to `docset.yml` that lets any repository fully replace the Elastic chrome with their own identity.

Scope: only affects isolated builds (e.g. GitHub Pages via the `isolated-build` action). Assembler and Codex builds are unchanged.

What

A new optional `branding` section in `docset.yml`:

```yaml
branding:
icon: my-icon.svg # relative to docs dir — copied to _static/
header-bg: "#336699" # header background colour (default when absent: #000000)
og-image: og.png # Open Graph social image — copied to _static/
```

When `branding` is present (all sub-fields optional), the following Elastic-specific chrome is suppressed:

Element Without branding With branding
Header Elastic logo + EUI primary blue Custom icon/title + custom colour
Footer Elastic logo, copyright, Terms/Privacy Hidden
OG `` image elastic.co social image Custom `og-image`, or omitted
Right nav Version dropdown, "Report a docs issue", "Learn how to contribute" All hidden
GitHub edit/tree links Hardcoded `elastic/` org Derived from actual git remote

Using `branding` together with `features.primary-nav: true` is a build error — the primary nav requires Elastic global navigation which is not available in white-label builds.

Safety

  • Branding images are validated on load: extension allowlist (`.svg`, `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.ico`), no path traversal (via `IFileInfo.IsSubPathOf`), no symlinks, existence check. Invalid values are nulled out with an error so the build fails loudly.
  • Basename collisions between `icon` and `og-image` are detected and reported.
  • `_static/` is created before copying assets so there is no `DirectoryNotFoundException`.
  • `GitHubRepository` normalises all common GitHub remote formats (HTTPS, `git@`, `ssh://git@`) to `org/repo` via structured helpers, with a safe `elastic/docs-builder` fallback.

Test plan

  • Add a `branding` block to a test `docs/docset.yml` and run `docs-builder isolated-build`; confirm custom header bg/icon, no Elastic footer, no Elastic OG tags, no right-nav Elastic links
  • Omit `icon` — header should show title text only, no Elastic logo
  • Omit `branding` entirely — all Elastic branding present (no regression)
  • Set an invalid image path (wrong extension, path traversal, symlink, missing file) — build emits errors
  • Set both `branding` and `features.primary-nav: true` — build emits error
  • Verify edit links and GitHub docs tree links use the correct `org/repo` for a non-Elastic repository

🤖 Generated with Claude Code

Allows any OSS repository using docs-builder to de-elasticise their
isolated/GitHub Pages output via a new `branding` section in docset.yml.

When `branding` is configured, the Elastic logo, footer, OG image tags,
"Report a docs issue" / "Learn how to contribute" right-nav links, and
the version dropdown are all suppressed. A custom header background colour
and icon image (copied to `_static/`) replace the Elastic-branded chrome.

Branding images are validated for allowed extensions, path traversal, and
symlinks during configuration loading.

Also fixes hardcoded `elastic/` org in GitHub edit links, docs links, and
the DeploymentInfo component — these now derive the correct org/repo from
the git remote via a new `GitHubRepository` property on
`GitCheckoutInformation`.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Mpdreamz Mpdreamz requested a review from a team as a code owner April 20, 2026 18:29
@Mpdreamz Mpdreamz requested a review from reakaleek April 20, 2026 18:29
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 07bf054d-ab9f-4d25-83c9-5aaf312b535e

📥 Commits

Reviewing files that changed from the base of the PR and between dc66fc5 and b235194.

📒 Files selected for processing (7)
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs
  • src/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsx
  • src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx
  • src/Elastic.Markdown/DocumentationGenerator.cs
  • src/Elastic.Markdown/HtmlWriter.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
✅ Files skipped from review due to trivial changes (1)
  • src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsx
  • src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx
  • src/Elastic.Markdown/DocumentationGenerator.cs
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Markdown/HtmlWriter.cs

📝 Walkthrough

Walkthrough

Adds optional white‑label branding support: a branding YAML section (icon, header-bg, og-image) is deserialized into DocumentationSetFile and exposed on ConfigurationFile. Branding values are validated (allowed extensions, path traversal, symlink, existence); invalid image fields are nulled and errors emitted. When present, branding is copied into the output _static directory (with collision checks). View models, Razor views, and web components receive branding data to conditionally render header, footer, TOC, Open Graph metadata, and to suppress Elastic-specific chrome.

Sequence Diagram

sequenceDiagram
    actor User
    participant ConfigLoader as Config Loader
    participant Validator as Branding Validator
    participant Generator as Doc Generator
    participant AssetCopier as Asset Copier
    participant ViewModel as View Model
    participant Layout as Layout Renderer
    participant Browser as Browser

    User->>ConfigLoader: Load documentation set config (includes branding)
    ConfigLoader->>Validator: Validate branding (extensions, paths, symlinks, existence)
    Validator-->>ConfigLoader: Return validated BrandingConfiguration (invalid fields => null + errors)
    ConfigLoader->>Generator: Provide configuration/context
    Generator->>AssetCopier: Branding present?
    alt Branding present
        AssetCopier->>AssetCopier: Copy icon and og-image into _static (check collisions)
        AssetCopier-->>Generator: Report copy results/errors
    else No branding
        AssetCopier-->>Generator: Skip
    end
    Generator->>ViewModel: Populate Branding and static paths
    ViewModel->>Layout: Supply branding props (icon, header-bg, og-image)
    Layout->>Browser: Render conditional header/footer/OG/meta
    Browser->>Browser: Display branded or default UI
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding white-label branding support for isolated builds, which is the core feature introduced across the changeset.
Description check ✅ Passed The description is well-related to the changeset. It explains the motivation, what was added (branding block in docset.yml), affected elements, safety measures, and provides a test plan—all of which directly align with the code changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/white-label

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Elastic.Markdown/Layout/_TableOfContents.cshtml (1)

40-50: ⚠️ Potential issue | 🟠 Major

Suppress “Report a docs issue” for branded builds.

This link still renders when Model.Branding is set, so white-label output keeps Elastic-specific chrome. Match the branding guard used for “Learn how to contribute”.

Proposed fix
-			`@if` (Model.BuildType != BuildType.Codex)
+			`@if` (Model.BuildType != BuildType.Codex && Model.Branding is null)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Elastic.Markdown/Layout/_TableOfContents.cshtml` around lines 40 - 50,
The "Report a docs issue" link is not suppressed for branded builds; update the
condition that currently checks only Model.BuildType (the block rendering the
"Report a docs issue" <li> with Model.ReportIssueUrl and the SVG) to also
require no branding by matching the guard used for the "Learn how to contribute"
link (e.g. wrap or change the if to check string.IsNullOrEmpty(Model.Branding)
or the same Model.Branding predicate used elsewhere), so the list item only
renders for non-branded outputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs`:
- Around line 275-315: ValidateBrandingImage currently uses StartsWith to check
escape paths and only emits errors without removing invalid values; change the
flow so ValidateBrandingImage returns a sanitized string?null for invalid inputs
and make ValidateBranding assign the return back to branding.Icon and
branding.OgImage in ValidateBranding. Replace the StartsWith check with a robust
relative-path check using Path.GetRelativePath(sourceDir, resolved) and treat
any relative path that begins with ".." or is rooted outside sourceDir as
invalid; also validate extensions, symlinks and existence as before, emit the
same errors, and return null when invalid so the calling ValidateBranding can
drop the value from the BrandingConfiguration.

In `@src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx`:
- Around line 46-103: The component uses headerBg as the sentinel for "branded"
mode which causes docsets that have branding defined but no icon/header-bg to
fall through to the default EuiHeaderLogo (see headerBg, iconSrc, and
logoSection); fix by accepting an explicit branded flag from the Razor view (or
ensure the view always passes a default headerBg when Branding exists) and use
that branded boolean to select the branded/title-only branches instead of
testing headerBg for null—update the component to check the new branded prop
when building logoSection and ensure the isolated header Razor view sets branded
= Model.Branding != null.

In `@src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml`:
- Around line 10-11: Replace the expressions that concatenate full attribute
strings for the web component with proper Razor attributes: instead of
outputting literal strings like header-bg="..." and icon-src="..." from the
Model.Branding?.HeaderBg and Model.BrandingIconStaticPath expressions, render
those values as attribute values on the element (assign Model.Branding?.HeaderBg
to the header-bg attribute and Model.BrandingIconStaticPath to the icon-src
attribute) and omit each attribute when its value is null; this ensures Razor
treats the values as attribute content (not pre-built strings) so they are
encoded correctly for the web component.

In `@src/Elastic.Documentation/GitCheckoutInformation.cs`:
- Around line 53-64: The current GitHub repo derivation in
GitCheckoutInformation (using Remote and RepositoryName) can return non-GitHub
remotes or unnormalized values; change the logic in the GitCheckoutInformation
partial record to (1) normalize SSH and HTTPS GitHub remotes (handle formats
like git@github.com:org/repo.git, ssh://git@github.com/org/repo.git,
https://github.com/org/repo.git), (2) strip a trailing .git, (3) validate that
the result is exactly an org/repo pattern (two path segments with no host), and
only return that; otherwise return the fallback "elastic/docs-builder". Add the
proposed regex/helper methods into the same partial record (e.g.,
IsGitHubRemote, ExtractGitHubOrgRepo, StripDotGit) and use them from the
existing code paths that reference Remote and RepositoryName so downstream
interpolation always receives a validated org/repo.

In `@src/Elastic.Markdown/DocumentationGenerator.cs`:
- Around line 212-225: The loop that copies branding images (iterating imagePath
over branding.Icon and branding.OgImage) currently writes both files to
_static/{source.Name}, causing silent overwrites when basenames collide; update
the logic in DocumentationGenerator.cs (the block using
Context.ReadFileSystem.FileInfo.New, source.CopyTo, and
_writeFileSystem.FileInfo.New) to either (a) preserve a namespaced relative path
when building destination (e.g., include the image's relative directory under
outputStaticDir) or (b) detect duplicate destination names before copying and
emit an error via Context.Collector.EmitError (or skip with a warning) so
duplicates are not overwritten—ensure the chosen behavior handles both
branding.Icon and branding.OgImage and uses source.Name and the original
relative path to compute a unique destination.

In `@src/Elastic.Markdown/HtmlWriter.cs`:
- Around line 137-138: The docs tree URL is hardcoded to "/docs" causing broken
links for repos that use a different documentation directory; update the logic
that sets gitHubDocsUrl (where gitHubRepo and gitBranch are used) to include the
actual DocumentationSourceDirectory value (the same relative path used by the
edit URL) instead of "/docs", ensuring you only build the tree URL when
gitHubRepo != "elastic/docs-builder" and gitBranch is valid and non-empty.

In `@src/Elastic.Markdown/Page/IndexViewModel.cs`:
- Around line 74-75: IndexViewModel instances are created without setting the
new nullable Branding property, so pages still show Elastic chrome; when
constructing IndexViewModel (wherever it is instantiated for markdown pages and
passed to HtmlWriter/HtmlWriter.cs) populate the Branding property from the
source view/context (e.g., propagate the existing Branding from the surrounding
PageViewModel/controller or layout model) by assigning Branding =
existingModel.Branding (or equivalent) on the IndexViewModel before passing it
to HtmlWriter; ensure the same propagation is applied wherever IndexViewModel is
constructed so the layout receives the branding value.

---

Outside diff comments:
In `@src/Elastic.Markdown/Layout/_TableOfContents.cshtml`:
- Around line 40-50: The "Report a docs issue" link is not suppressed for
branded builds; update the condition that currently checks only Model.BuildType
(the block rendering the "Report a docs issue" <li> with Model.ReportIssueUrl
and the SVG) to also require no branding by matching the guard used for the
"Learn how to contribute" link (e.g. wrap or change the if to check
string.IsNullOrEmpty(Model.Branding) or the same Model.Branding predicate used
elsewhere), so the list item only renders for non-branded outputs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bbaaf9d3-b750-41cf-9cda-a552eb085572

📥 Commits

Reviewing files that changed from the base of the PR and between a400430 and 2b80c76.

📒 Files selected for processing (14)
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Documentation.Configuration/Toc/DocumentationSetFile.cs
  • src/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsx
  • src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx
  • src/Elastic.Documentation.Site/Layout/_Head.cshtml
  • src/Elastic.Documentation.Site/Layout/_IsolatedFooter.cshtml
  • src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml
  • src/Elastic.Documentation.Site/_ViewModels.cs
  • src/Elastic.Documentation/GitCheckoutInformation.cs
  • src/Elastic.Markdown/DocumentationGenerator.cs
  • src/Elastic.Markdown/HtmlWriter.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Markdown/Page/Index.cshtml
  • src/Elastic.Markdown/Page/IndexViewModel.cs

Comment thread src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx Outdated
Comment thread src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml Outdated
Comment thread src/Elastic.Documentation/GitCheckoutInformation.cs Outdated
Comment thread src/Elastic.Markdown/DocumentationGenerator.cs
Comment thread src/Elastic.Markdown/HtmlWriter.cs Outdated
Comment thread src/Elastic.Markdown/Page/IndexViewModel.cs
Mpdreamz and others added 2 commits April 20, 2026 20:45
…tection

- ConfigurationFile: ValidateBrandingImage now returns string?/null for
  invalid inputs; ValidateBranding assigns results back to branding.Icon /
  branding.OgImage so downstream code never sees invalid paths. Replaced
  StartsWith path-escape check with IFileInfo.IsSubPathOf() extension.

- GitCheckoutInformation: Replace ad-hoc string manipulation with
  ExtractGitHubOrgRepo / NormalizeToGitHubPath helpers that handle all
  GitHub remote formats (HTTPS, git@, ssh://git@), strip .git, and
  validate the result is exactly org/repo before returning it.

- Header.tsx: Add explicit branded boolean prop (r2wc 'boolean') so the
  Razor view controls branded-mode selection directly; removes the fragile
  headerBg-as-sentinel pattern. Isolated header template now passes
  branded="true"/"false" and binds header-bg/icon-src as plain attribute
  values for correct Razor HTML encoding.

- DocumentationGenerator: Detect basename collisions between branding
  icon and og-image before copying; emit an error and skip on duplicate.

- HtmlWriter: Tree URL now uses the real relative docs directory path
  (same computation as the edit URL) instead of the hardcoded "/docs".

- _TableOfContents: Also suppress "Report a docs issue" for branded builds.

- Prettier + dotnet format: fix DeploymentInfo.tsx formatting and
  IndexViewModel.cs import ordering.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
primary-nav injects the Elastic global navigation bar, which is not
available in white-label isolated builds. Emit a build error when both
are configured together.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Mpdreamz
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Elastic.Markdown/DocumentationGenerator.cs`:
- Around line 203-237: CopyBrandingResources can attempt to copy branding images
into a non-existent _static directory causing DirectoryNotFoundException (and
runs for Codex/Assembler even when they shouldn't); update CopyBrandingResources
to first skip copying unless the current build type requires it (check
Context.Configuration.BuildType or equivalent to only run for HTML/exporting
builds) and before copying ensure the destination directory exists by creating
outputStaticDir via the write filesystem (use _writeFileSystem.DirectoryInfo/New
or Directory.CreateDirectory equivalent) so the directory is created prior to
any source.CopyTo calls; keep the existing collision checks (seen HashSet) and
logging but gate and create the directory before any copy operations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 259a219c-cd67-4943-a849-02624b2669a9

📥 Commits

Reviewing files that changed from the base of the PR and between 2b80c76 and 39b39db.

📒 Files selected for processing (9)
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Documentation.Site/Assets/web-components/Header/DeploymentInfo.tsx
  • src/Elastic.Documentation.Site/Assets/web-components/Header/Header.tsx
  • src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml
  • src/Elastic.Documentation/GitCheckoutInformation.cs
  • src/Elastic.Markdown/DocumentationGenerator.cs
  • src/Elastic.Markdown/HtmlWriter.cs
  • src/Elastic.Markdown/Layout/_TableOfContents.cshtml
  • src/Elastic.Markdown/Page/IndexViewModel.cs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Elastic.Documentation.Site/Layout/_IsolatedHeader.cshtml
  • src/Elastic.Documentation.Configuration/Builder/ConfigurationFile.cs
  • src/Elastic.Markdown/Page/IndexViewModel.cs
  • src/Elastic.Markdown/HtmlWriter.cs

Comment thread src/Elastic.Markdown/DocumentationGenerator.cs
CreateDirectory is idempotent so this is safe even when the directory
was already created by ExtractEmbeddedStaticResources.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Mpdreamz
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Header.tsx: non-branded header now uses main's light gradient+shadow
styling; branded header retains solid custom bgColor. Non-branded
EuiHeaderLogo updated to main's light-mode colours (dark textInk,
hover background).

_TableOfContents.cshtml: version dropdown, report-an-issue, and
learn-how-to-contribute now require both Model.Features.PrimaryNavEnabled
(from main) and Model.Branding is null (from this branch).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@Mpdreamz
Copy link
Copy Markdown
Member Author

Mpdreamz commented May 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Member

@theletterf theletterf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@Mpdreamz
Copy link
Copy Markdown
Member Author

Mpdreamz commented May 6, 2026

Merging this in as experimental!

@Mpdreamz Mpdreamz merged commit c846692 into main May 6, 2026
24 checks passed
@Mpdreamz Mpdreamz deleted the feature/white-label branch May 6, 2026 09:32
Mpdreamz added a commit that referenced this pull request May 11, 2026
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants