Add an experimental shared chrome build mode#2662
Merged
chalin merged 43 commits intoJun 25, 2026
Merged
Conversation
- Contributes to google#2659 - Adds a `navfragment` output format that emits each docs section's left-nav as a standalone, chrome-free fragment (`docs/_nav.html`) — the basis for re-rendering nav client-side so a lean build stays functionally equivalent to a full build. - Ships the format definition only; a site opts its docs section in via `outputs`, like the `print` format. - Forces the cached/shared sidebar shape, so one fragment serves every page under a sidebar root (active state is applied client-side later). - Extracts sidebar root/args resolution into a shared `sidebar-args.html` partial, reused by `sidebar.html` and the fragment template (DRY, behavior-preserving). - Adds a fixture-site test: the fragment carries the nav and its internal links, with no page chrome.
- Contributes to google#2659 - Adds an `else` branch to `sidebar.html`: when lean render drops the inline left-nav on an inner page and the site has opted the sidebar root into the `navfragment` output format, it emits a `data-nav-src` placeholder for the client to fetch + inject; sites that haven't opted in emit nothing, so lean behavior is unchanged. - Discovers the fragment URL via `sidebarRoot.OutputFormats.Get "navfragment"`, reusing the shared `sidebar-args.html` root resolution, so the placeholder and the emitted fragment always agree. - Covers it with a fixture test: a lean inner page carries the placeholder and no inline `#td-section-nav`, while the docs landing keeps its inline-nav exemplar with no placeholder.
- Contributes to google#2659 - Adds `assets/js/csr-nav.js`, a vanilla (no-jQuery) module that makes a lean build's left-nav match a full build's once it runs: on a lean inner page it fetches the section's `navfragment` and injects it where the placeholder was; on the cached/shared inline nav it hydrates in place. Either way it applies the active-page state client-side. - Computes active state from `location.pathname` (href match), not a server-baked id, so one shared fragment serves every page under a sidebar root. - Replaces the inline jQuery active-state `<script>` in `sidebar.html` with that module, so the injected and cached navs share one vanilla implementation (DRY). - Concatenates `csr-nav.js` into the `main.js` bundle (`scripts.html`). - Adds a `csr` build env to docsy.dev that opts the docs section into `navfragment` and forces the cached path, so the feature can be exercised end-to-end.
- Contributes to google#2659 - Adds a fixture-site test that builds the same site full and lean, runs the shipped `csr-nav.js` in jsdom over the lean output, and asserts the hydrated left-nav matches the full build — same entries, same active link, same active-path trail — for both the injected (inner page) and cached-inline (docs landing) paths. - Adds `jsdom` as a devDependency for the in-Node DOM the equivalence check needs.
- Contributes to google#2659 - Replaces the per-section `navfragment` output format with a zero-config donor fetch: a lean inner page points at the kept docs landing via `data-nav-donor`, and `csr-nav.js` fetches it, extracts the left-nav, caches it in `sessionStorage`, and injects it — no site has to opt a section into an output format. - Adds a `csr` value to `td.lean_render` that drops the repeated nav like `remove` but emits the donor placeholder; `remove` behavior is unchanged. - Forces the neutral cached nav shape in `csr` mode so one shared nav serves every page, with active state applied client-side from the URL. - Retires the `navfragment` output format, its `list.navfragment.html` template, and its test. - Covers the contract with fixture tests (donor placeholder, neutral landing nav, no fragment file) and jsdom equivalence tests (hydrated lean nav matches a full build on inner and landing pages). - Notes a trade-off: the donor is the docs landing, so `sidebar_root_enabled` scoped pages show the full tree rather than a scoped subtree — acceptable for the POC.
- Contributes to google#2659 - Adds a `csr` npm decorator (mirrors otel.io's `lean`) that sets the CSR build mode, so `npm run -C docsy.dev csr -- serve|build|check:links` exercises the client-side nav locally. - Drops the `config/csr` dogfood env now that CSR needs no output-format opt-in or sidebar-cache config.
- Contributes to google#2659 - Restores scoped left-navs on lean/CSR builds so a `sidebar_root_for` page matches its full-build subtree (same links, same active item, root item links up). - Keeps the single docs-landing donor (max cache reuse); a scoped page just carries a `data-nav-root` hint, and the client re-roots a fresh copy of the donor tree to that subtree. - Adds an isolated `reRootMenu()` in `csr-nav.js`, kept self-contained so it's easy to review or drop; re-roots in-document so hrefs resolve absolute. - Sets the server `scoped` flag only where `sidebar_root_for` actually narrows the nav, leaving the `children` landing on the full tree. - Adds a fixture test covering the build contract (hint present/absent) and jsdom functional equivalence (CSR-lifted subtree vs full self-root build).
- Contributes to google#2659 - Adds an exploratory harness that inlines a CSR (lean) build by running the real shipped `csr-nav.js` in jsdom (fetch -> inject -> re-root -> hydrate), then diffs each page against a full build through the same serializer. - Confirms the left-nav is structurally equivalent: the raw nav diff reduces to exactly three equivalent-by-construction factors (vestigial Bootstrap `show`, `checked` attribute vs property, class-token order), and canonicalizing those yields an exact match. - Shows the only real whole-page gap is the navbar and footer, which CSR does not yet restore. - Writes scratch artifacts under ./tmp/equiv-out for inspection; spike only, not wired into the test suite yet.
- Contributes to google#2659 - Makes the CSR client's nav reveal foldable-aware, so an inlined lean page is structurally identical to a full render: - Sets the same `checked` attribute the server bakes (foldable nav, the docs default) rather than only the DOM property, and drops the vestigial Bootstrap `show` - Leaves non-foldable behavior (sibling/child `show` expansion) unchanged - Adds a shared equivalence engine reused by the spike tool and tests: runs the real shipped client in jsdom and compares both sides modulo class-token order and ignorable whitespace - Adds a hermetic fixture test proving the inlined left-nav matches the full build, with the navbar and footer the only remaining whole-page gap - Refactors the spike tool onto the shared engine and defaults its scratch output under ./tmp
- Contributes to google#2659 - Restores the page-invariant chrome a lean build drops, so an inlined CSR page matches a full render beyond the left-nav: - Footer — copied verbatim from the home donor (page-invariant) - Navbar — copied from the home donor, then the active top-level link re-derived from the URL (longest-prefix match) - Ships a placeholder for the navbar's per-page version/language selectors, whose links can't be restored from the home donor without re-deriving them — a documented, temporary feature interaction - Extends the shared equivalence engine to compare named regions and the whole page with those selector menus neutralized; the fixture test now covers the footer, navbar, selector placeholder, and whole page - Shares one site title across the two fixture builds so the navbar brand doesn't diverge
- Replaces the string param `td.lean_render` (`remove`|`csr`) with a boolean `td.csr_enable`, retiring `remove` now that CSR supersedes the lean-build feature. - Adds a `csr-enabled.html` helper that normalizes the value: Hugo only casts an env-var override to bool when the key already exists in site config, so a theme default doesn't help and a bare truthiness check would treat the string `"false"` as true. - Documents the feature in a new "Client-side chrome rendering" page and slims the link-checking guide to point at it (one home per concern). - Renames partials and tests (`lean-render` -> `csr-render`); fixture suite gains a guard that env `csr_enable=false` keeps all chrome. - Updates docsy.dev link-check CI to set `HUGOxPARAMSxTDxCSR_ENABLE=true`.
- Restores the per-page navbar selectors the home donor can't supply, replacing the temporary placeholder: version links exactly (strip the donor's path, append this page's), language links by prefix-swap (functional; exact when translations share slugs and all exist). - Compares the version selector in the structural equivalence test now that it's restored exactly; keeps the language selector neutralized there (documented best-effort caveat). - Adds csr-selectors.test.mjs (version exact + language prefix-swap); fixture suite at 22/22.
- Contributes to google#2659 - Adds `tests/site-equivalence/full-vs-csr.mjs`, which builds one git tree from a full build, commits it as the baseline, then overlays the CSR build after running the real shipped client over each page in jsdom — so `git diff` is the scoreboard of what's not yet equivalent. - Skips jsdom on pages with no CSR markers (kept as-is) and supports a `--canonical` mode that sorts class tokens and neutralizes the best-effort language selector, isolating genuine structural gaps from cosmetic noise. - Reuses the existing equivalence engine (`lib/equivalence.mjs`); leaves the tree and unified diff under ./tmp for inspection.
- Contributes to google#2659 - Bakes per-page navbar traits as `data-navbar-cover`/`data-navbar-theme` hints on the CSR placeholder, since the restored navbar is the home donor's and may not share them — fixing the cover-translucent + `data-bs-theme="dark"` leak onto inner pages. - Adds `applyNavbarCoverTheme()` in csr-nav.js, applied after the swap and before active-link re-derivation. - Adds a fixture test (cover-home + dark-theme) guarding the no-leak result.
- Contributes to google#2659 - Left-trims the sidebar CSR placeholder's closing `{{- end -}}` so the lean build no longer emits a double newline before `</aside>`, clearing a whitespace-only diff against the full build on ~130 docsy.dev pages.
- Re-enables the self-rooted sidebar on Deployment, giving the CSR full-site equivalence harness a real `sidebar_root_for` test case. - Previously removed pending better ancestor navigation; kept here as a CSR scaffold on the feature branch.
- Contributes to google#2659 - Bakes the page's logical (HTML-output) URL as `data-canonical-path` on the navbar placeholder; the client keys active state and the version selector to it instead of `window.location`, fixing print (`_print/*`) and paginated (`/page/N/`) pages (CSR-15, CSR-16) - Falls back to the nearest-ancestor nav entry (longest prefix) for an out-of-tree `toc_hide` page absent from its own nav, marking the active-path trail a full build derives from the page tree (CSR-17) - Adds TDD fixtures `csr-canonical` and `csr-out-of-tree`
- Contributes to google#2659 - On a re-rooted scoped sidebar (`sidebar_root_for`), advertises `data-sidebar-root-id` and flags the root link's label with the up-icon class, matching a full scoped build (CSR-13) - Leaves the structural residual (the promoted root's checkbox/label wrapper and ul-level numbering) for a later JSON doctree (CSR-14) - Extends `csr-scoped` with the marker assertions
- Contributes to google#2659 - Tags the CSR fixture tests with their case-registry IDs (`CSR-NN`) so the case-to-test link is greppable both ways - Adds `csr-no-left-sidebar`, an equivalence guard mirroring `tests/layouts/no-left-sidebar` (CSR-18)
- Contributes to google#2659 - Neutralizes the `local-time` widget's baked build time (`new Date("…")`) in the scoreboard's canonicalization, so two builds run seconds apart no longer diff on it — a measurement artifact, not a CSR difference - Scrubs private working-notes paths from a client and a harness comment
…gelog - Contributes to google#2659 - Removes `sidebar_root_for: self` from the Deployment section: the scoped re-root's structural residual (donor checkbox/label wrapper, ul-level renumber) is the lone deferred equivalence case, so dropping the live example keeps the full-vs-CSR scoreboard green until it lands - Reframes the changelog's experimental entry from "Lean render" to "Client-side chrome rendering (CSR)", pointing at the new CSR guide
- Contributes to google#2659 Doc-driven rename, ahead of the implementation: present the feature as a build mode -- td.chrome = full | shared (default full) -- rather than a client-side rendering (CSR) toggle. The code still reads td.csr_enable; the TDD implementation follows in a later commit. - Move the user guide page from content/csr.md to deployment/chrome-modes.md (it is a build mode, best placed under Deployment and previews); retitle to "Chrome build modes". - Frame full vs shared, with shared restoring chrome from one donor page per locale; audience is dev, previews, and link/diff checks (not production deploys yet, since inner pages need JS to restore their chrome). - Env override simplifies to HUGO_PARAMS_TD_CHROME=shared (no x-delimiter needed now that the chrome key has no underscore); verified on Hugo 0.163. - Update the link-checking best-practice page and the changelog entry, and point their links at the new page path.
- Contributes to google#2659 Follow-up to the docs-first rename, applying review feedback: - Rename deployment/chrome-modes.md to chrome.md (slug /docs/deployment/chrome/). - Drop the best-practices link-checking page; the chrome page now covers it. Delink its reference and repoint the changelog link to the new slug. - Add a one-line app-shell/MPA aside for web developers. - Keep chalin's prose edits (build-time cost up front, single-donor emphasis, YAML-first config tabs).
- Contributes to google#2659 Implement the build-mode rename the docs already describe: replace the boolean td.csr_enable toggle with a td.chrome string param whose values are `full` (default) and `shared`. - Gate (_partials/csr-render.html, sidebar-args.html): the bool-truthiness idiom `in (slice true "true" 1)` becomes a plain `eq (.Param "td.chrome") "shared"`. A string enum sidesteps the Hugo env-var bool-typing footgun entirely, so the normalization comment goes away. - Tests: env override is now HUGO_PARAMS_TD_CHROME=shared (no x-delimiter needed); the config path uses chrome: shared. The former "false"/"TRUE" footgun guards become a `full` keeps-chrome test and an unrecognized-value (case-sensitive) safe-fallback test. 33/33 fixture-site tests pass. - docsy.dev: the `csr`/`precheck:links:lean` scripts and the link-check matrix set HUGO_PARAMS_TD_CHROME=shared. - Registry: CSR-01 row reflects the new gate; repoint the moved user-doc link. Internal `csr` identifiers (partial/test filenames, the `csr` npm script, the tasks/0.16/csr folder, CSR-NN case IDs) are intentionally left for a later rename pass.
- Contributes to google#2659 - Change the web-dev callout from a TIP to a NOTE. - Point "app-shell pattern" at its canonical definition (developer.chrome.com/blog/app-shell) and "multi-page site" at the web.dev PWA architecture page, instead of linking both phrases to the same URL.
shared chrome build mode (td.chrome)
- Contributes to google#2659 Follow the public td.chrome rename through to the internal identifiers, so no "CSR" naming (which collided with framework client-side rendering) remains: - Files use the full word `chrome`: csr-nav.js -> chrome-nav.js, csr-render.html -> chrome-render.html, the 11 csr-*.test.mjs -> chrome-*.test.mjs, and full-vs-csr.mjs -> full-vs-shared.mjs (it compares the full and shared modes). - The case registry uses the acronym CCR (client-side chrome restoration): tasks/0.16/csr/ -> tasks/0.16/ccr/, and the CSR-NN case IDs -> CCR-NN. - DOM/storage markers: td-csr-chrome-placeholder -> td-chrome-placeholder, td-sidebar-csr-placeholder -> td-sidebar-chrome-placeholder, cache prefix td-csr-nav: -> td-chrome-nav: (emitter, client, and tests in sync). - Identifiers and prose: inlineCsr -> inlineChrome, csrNavSrc -> chromeNavSrc, the harness's csr* build vars -> shared*, the `csr` npm script -> `ccr`, and "CSR (off/on/mode/build)" comments -> "full/shared mode". Behavior is unchanged; 33/33 fixture-site tests pass and a shared-mode docsy.dev build emits the renamed markers and bundles the renamed client.
- Contributes to google#2659 - Updates the docs/content section golden, which lost its Client-side chrome rendering child entry when that page moved to /docs/deployment/chrome/. - Adds the developer.chrome.com app-shell link (cited in the page's web-dev note) to the refcache.
9378a10 to
6e34bde
Compare
shared chrome build mode (td.chrome)shared chrome build mode
- Contributes to google#2659 - Notes on the chrome build-modes page that `shared` mode currently targets the docs section, so other sections (such as blog) aren't a validated target yet and should keep `full`. - Reworks the `sidebar_cache_limit` entry in navigation.md: the cached side nav's active-state pass is now applied by the same client script that powers `shared` mode, and links to the chrome build-modes page.
…, and tests - Contributes to google#2659 - Renames the stale `csr-nav.js` reference and the `[csr]` link label in the changelog to `chrome-nav.js` and `[chrome]`, and updates the two `chrome-nav.js` console warnings from the `csr-nav:` prefix to `chrome-nav:`. - Renames the `check:links:lean` and `precheck:links:lean` scripts to `:ccr`, matching the feature acronym. - Renames the leftover `csr` identifiers in the fixture-site tests: the client-restored build variable becomes `ccr` (with "CCR build" assert messages), and the `buildSite` labels follow the `td.chrome` modes — `chrome-full` / `chrome-shared` for the nav-equivalence pair, `<scenario>-ccr` alongside `<scenario>-full` for the scenario pairs, and `ccr-<name>` for the single-build cases.
- Contributes to google#2659 - Notes on the `full` bullet that any `td.chrome` value other than `shared` is treated as `full`, so a typo fails safe. - Closes the CI recipe loop: after a `shared` build, point your link checker at the generated `public/` output as usual, with no checker-specific config.
- Contributes to google#2659 - Replaces the old "lean (build/render)" wording with "shared" in the fixture-site and site-equivalence test comments and in the `chrome-nav.js` and `chrome-render.html` source comments, completing the move off the feature's former name. Comment-only; no behavior change. - Leaves the plainly descriptive uses (the docs' "lean output"), which aren't the retired mode name.
… partial - Contributes to google#2659 - Adds a known-limitation test to `chrome-selectors`: on a page with no translation in a locale, a full build disables that language item while shared mode restores a live link to the non-existent page. The test pins the current divergence and will flip to a parity assertion once the real fix lands. - Reclassifies CCR-10 from `restored` to a new `partial` status in the case registry (the common case matches a full build; the missing-translation case diverges), and records the planned fix: bake each page's available locales into the navbar placeholder and emit a disabled item for the rest.
6 tasks
- Contributes to google#2659 - Adds `chrome-reachability`: a static-HTML test (no jsdom restoration) that asserts a `shared` build exposes the same unique chrome links to a no-JS checker as a full build, so the donor pages leave none unreachable. This was the one headline claim covered only by the docsy.dev diagnostic matrix, not the suite. Cited under CCR-02 in the case registry. - Renames the whole-page equivalence test to "canonicalized ... except documented neutralizations" and documents what `bodyWithout` neutralizes (language selector, class-token order, whitespace, build timestamp), now that the CCR-10 divergence is pinned. - Scopes the canonical test's header to "simulated via a print-style URL" and the registry to note CCR-15/CCR-16 are mechanism-validated (no real print/paginator output yet) and CCR-19 is covered indirectly; a real print/paginator fixture is a tracked follow-up. - Clarifies the build test title to "no extra nav-fragment opt-in needed".
- Contributes to google#2659 - Configures the reachability fixture's footer (`params.links` user + developer) and adds an external navbar menu item, so all three link-bearing chrome regions (navbar, footer, left-nav) carry both internal and external links. - Widens the reachable-link set to include external targets (normalized to `origin + path`), so the test matches its title: a no-JS `shared` build reaches every chrome link — internal and external — that a full build does, via the kept donors. Asserts the inner page really drops the footer/navbar externals so reachability is carried by the donor, not trivially.
- Contributes to google#2659 - Renames the matrix's `lean` cell, variable, comments, and report strings to `shared`, matching the `td.chrome = full | shared` build mode used everywhere else (the earlier lean-term sweep covered tests and theme source, not this docsy.dev script). The cell still sets `HUGO_PARAMS_TD_CHROME=shared`; only the label changes, so behavior is unchanged.
- Contributes to google#2659 - The help extractor started at the blank line after the shebang and broke immediately (a blank line isn't a `//` comment), so `--help` printed an empty string. Skip leading blank lines and collect the contiguous comment block once it starts, so the usage text prints as intended.
- Contributes to google#2659 - The site-equivalence inline-and-diff helper's usage block still built the shared site with `npm run chrome build`, but that npm decorator was renamed to `ccr`. Switch to the direct `HUGO_PARAMS_TD_CHROME=shared npm run build -- -d` form (matching full-vs-shared.mjs), which also sidesteps the decorator swallowing the `--` that forwards `-d`.
…onical - Contributes to google#2659 - Makes the whole-site full-vs-shared scoreboard canonical by default (the structural-equivalence bar that neutralizes class-token order, the best-effort language selector, and the build timestamp); adds `--no-canonical` for a stricter raw diff. Without canonicalization the docsy.dev run reports ~141 false diffs that are pure timestamp/class-order noise. - Lets the script build docsy.dev both ways itself when `--full`/`--shared` aren't given, so it works out of the box; explicit dirs still reuse existing builds for fast iteration. Adds `--check` to exit non-zero when any page differs. - Wires `npm run test:site-equivalence` (= the script with `--check`) so the whole-site check runs with sane defaults, mirroring `npm run test:smoke`. Like smoke, it's slow (two Hugo builds + jsdom over every page) and stays out of CI and the default `npm test`.
chalin
added a commit
to chalin/docsy
that referenced
this pull request
Jun 26, 2026
- Contributes to google#2615 - Adds an experimental "shared chrome build mode" section to the 0.16.0 release report, with a highlights entry, a release-summary bullet, and an Actions block; links to the Chrome build modes guide and flags it experimental (google#2659, google#2660, google#2662). - Bumps the project Hugo build from 0.163.1 to 0.163.2 across both posts (google#2658). - Documents the 0.163.2 PostCSS/Netlify ERR_ACCESS_DENIED fix and the niche Pandoc/reStructuredText build-failure change in the Hugo upgrade guide.
chalin
added a commit
to chalin/docsy
that referenced
this pull request
Jun 26, 2026
- Contributes to google#2615 - Bumps last-main-commit to b3ce927 and adds ledger rows for the five commits since f312828: Hugo 0.163.2 (google#2658), the Russian locale sync (google#2590), lean-render (google#2660), the link-check matrix (google#2661), and the shared chrome build mode (google#2662). - Records the shared chrome build mode as the headline experimental feature, routed as one blog section spanning google#2660 and google#2662, with google#2661 as internal tooling. - Updates the Hugo theme summary and decisions for the 0.163.2 build bump.
chalin
added a commit
to chalin/docsy
that referenced
this pull request
Jun 26, 2026
- Renames the stale "mono-site" wording to "fixture-site" in the 0.16.0 release post, matching the test harness renamed on main. - Adds google#2660 and google#2662 to the changelog's experimental shared-chrome entry for traceability, matching the blog post and the other changelog entries. - Updates the chrome-render.html comment from "configured chrome" to "config-defined" to match the published Chrome build modes docs.
chalin
added a commit
to chalin/docsy
that referenced
this pull request
Jun 26, 2026
- Renames the stale "mono-site" wording to "fixture-site" in the 0.16.0 release post, matching the test harness renamed on main. - Adds google#2660 and google#2662 to the changelog's experimental shared-chrome entry for traceability, matching the blog post and the other changelog entries. - Updates the chrome-render.html comment from "configured chrome" to "config-defined" to match the published Chrome build modes docs.
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.
sharedchrome build mode (client-side chrome restoration) #2659td.chrome = full | shared(defaultfull). Insharedmode the build emits the repeated navbar, footer, and left side-nav on one donor page per locale, and a small shipped script restores them in the browser on the other pages — so one build serves both readers (complete pages) and link checkers (each shared link emitted once).fullfor production,sharedfor the builds whole-site tooling consumes (local dev, deploy previews, link-checking, output diffing). It's an MPA take on the app-shell pattern; no new build artifact and zero required site config.theme/assets/js/chrome-nav.js, vanilla JS,sessionStorage-cached) injects each region and re-derives per-page state: active link/path + foldable reveal, version/language selectors, scoped (sidebar_root_for) re-rooting, and canonical-URL active state on print/paginated pages.tests/site-equivalence/full-vs-shared.mjs) client-restores asharedbuild of docsy.dev and diffs it against a full build: 0 differing pages.