Skip to content

Build: gulp 5 / Antora 3.1.15 / Yarn 4.18, DocSearch v5, and UI preview tooling - #1729

Open
ammachado wants to merge 19 commits into
apache:mainfrom
ammachado:build/gulp-5-and-antora-3.1.15
Open

Build: gulp 5 / Antora 3.1.15 / Yarn 4.18, DocSearch v5, and UI preview tooling#1729
ammachado wants to merge 19 commits into
apache:mainfrom
ammachado:build/gulp-5-and-antora-3.1.15

Conversation

@ammachado

@ammachado ammachado commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

What this does

Five related build and UI workstreams, each independently reviewable:

  1. gulp 5 + Antora 3.1.15 upgrade, plus a silent bug found along the way.
  2. Yarn 4.1.0 to 4.18.0 with tightened supply-chain settings.
  3. Search migrated from a hand-rolled Algolia client to DocSearch v5.
  4. UI fixes found while checking the new search widget in a browser: admonitions, pagination, and a Camel theme for DocSearch.
  5. Preview and test tooling, including the fix for why item 4's admonition bug looked worse than it was.

1. gulp 5 and Antora 3.1.15

The UI bundle was being built empty

antora-ui-camel/gulp.d/tasks/build.js is an async function that returned a stream. gulp settles an async task on its promise, and a stream is not a thenable, so bundle:pack zipped a directory that had not been written yet.

Finished 'build' ui-bundle.zip
before 26 ms 22 bytes, 0 files
after 1.92 s 697689 bytes, 124 files

This did not affect the published site: antora-playbook-production.yml consumes the staged antora-ui-camel/public/_ directory, not the zip. It did affect anything using the --ui-bundle-url that the gulp task prints.

Theme to gulp 5

Moved to gulp 5, vinyl-fs 4, undertaker 2, through2 5 and fs-extra 11. Three things needed more than a version bump:

  • merge-stream had to go. vinyl-fs 4 is built on streamx, and merge-stream is a node-core PassThrough that silently drops streamx sources. Reproduced standalone: 93 files in, 3 out. Replaced with ordered-read-streams, the streamx-native merge from the same org as gulp.
  • Binary assets need encoding: false, since gulp 5 decodes contents as UTF-8 by default. All 58 fonts and images were checked byte-for-byte against the gulp 4 output and are identical.
  • Removed the font/*.{ttf,woff*(2)} glob. src/font has never existed in this repo's history; fonts are copied into dest/font by the postcssUrl handler. vinyl-fs 3 ignored the missing directory, vinyl-fs 4 raises ENOENT.

PostCSS 7 plugin API to PostCSS 8

cssnano@8 returns a plugin object rather than a callable, and the bare (css, result) => ... plugins were PostCSS 7 style and were no longer being invoked at all. Rewritten as visitor plugins. 97 transitive packages pruned.

postcss-custom-properties@15 keeps the :root block after substitution where v9 removed it, so resolved custom properties are now dropped explicitly by a small local plugin (gulp.d/lib/drop-resolved-custom-properties.js).

Note for reviewers: browserslist: last 2 versions resolves to include ie 11, ie 10, ie_mob and op_mini all, none of which support CSS custom properties. Dropping postcss-custom-properties entirely is therefore not an option; it would be both larger and broken on those targets.

Root gulpfile to gulp 5

Replaced del with native fs.rm (del 7+ is ESM only) and dropped the dependency. Fixed generate-markdown signalling completion twice, by both calling done() and returning a promise.

Antora 3.1.15

Antora 3.1.9 added a heuristic that warns when an extension's register function names its first parameter registry:

const ASCIIDOCTOR_REGISTER_FUNCTION_RX = /^(?:(?:function(?: +register)? *)?\( *registry *[,)])/

@djencks/asciidoctor-jsonpath and @djencks/asciidoctor-antora-indexer both do. Antora still registers them, the warning is advisory, but this playbook sets runtime.log.failure_level: warn, so the warning alone failed the build. Both packages were last published in 2022 and cannot be fixed upstream, so they are now required through thin wrappers in extensions/, matching the three local extensions already there.

Worth knowing if anyone pins differently: 3.1.11 skips these extensions entirely rather than warning. 3.1.12 through 3.1.15 restored warn-and-register.

Unrelated CSS fix

Removed max-width: var(--static-max-width) from .blog. That variable was never declared, only --static-max-width--desktop is, so the declaration was invalid at computed-value time and max-width already resolved to none. Removing it is a no-op and matches .static, which constrains width only inside the desktop media query.


2. Yarn 4.1.0 to 4.18.0

Fixes the stale compat/typescript patch-hunk warning during install: the patch hash no longer matched typescript 5.9.3 under the old Yarn.

Supply-chain settings tightened at the same time:

  • approvedGitRepositories: [], since this repo has no git-protocol dependencies.
  • npmMinimalAgeGate: 4320 (3 days), delaying trust in newly published npm versions.

3. Search: hand-rolled Algolia client to DocSearch v5

The site previously shipped src/js/vendor/algoliasearch.bundle.js, a 365-line hand-rolled search UI built on the raw algoliasearch v4 client, along with 169 lines of bespoke result-dropdown CSS in src/css/header.css. Both are removed in favor of @docsearch/js 5 and @docsearch/css 5, which is the officially supported integration for the DocSearch-crawled index this site already uses.

Result curation had to be carried across

The old bundle did three things beyond querying, and a straight swap to DocSearch would have silently dropped all of them. They are reimplemented in src/js/08-docsearch.js:

  • Sub-project exclusion. /camel-k/, /camel-quarkus/, /camel-spring-boot/, /camel-kafka-connector/, /camel-kamelets/ and /camel-karaf/ stay out of the main index results; those docs are browsable directly.
  • Core-docs ranking, floating /manual/, /user-guide/, /architecture/, /getting-started/ and /faq/ above /components/. This works through transformItems because DocSearch groups by hierarchy.lvl0 into an insertion-ordered plain object, so the order we emit decides both group order and within-group order.
  • Per-page capping at 2 hits, replacing the old query-aware deduplicateHits. transformItems never receives the query, and the original only used it to detect a direct parent match, so capping by page reaches an equivalent outcome without it.

Why hitsPerPage: 50

The index has no attributeForDistinct and no attributesForFaceting, so both filters must run client side, on an already-truncated window of hits. Distinct parent pages surviving the sub-project filter, measured against the live index:

query hitsPerPage 20 (DocSearch default) hitsPerPage 50
kamelet 2 pages 3 pages
timer 1 page 7 pages
rest dsl 7 pages 16 pages

At the default, timer returned 19 usable hits spanning a single document, so the first result group was five near-identical anchors off one page.

The durable fix is at the index level rather than in the browser. Filed as CAMEL-24396; with server-side distinct the over-fetch and most of the client-side logic can be deleted.

Note that .docsearch.config.json in this repo already attempts both settings, but they are not in effect on the live index, and one of them (attributeForDistinctResults) is not a real Algolia setting name. The records also lack url_without_anchor, so there is currently no anchor-free attribute to group on. Details in the ticket; nothing there blocks this PR.

@docsearch/css gets its own build stream

Routing the vendor stylesheet through postcss-custom-properties with preserve: false flattened its runtime cascade: the @media (width<=768px) :root overrides were inlined at desktop values, --shimmer-bg was dead, and the 2.5 KB :root[data-theme=dark] block still shipped but could never take effect.

It now builds separately, skipping the custom-property pass, revved to css/vendor/docsearch.css and linked ahead of site.css in both the Antora and Hugo head partials. This mirrors how the DocSearch UMD bundle already ships as vendor JS.

preserve: true was rejected deliberately: it fixes the same bug but costs about 3.8 KB gzip site-wide and would reverse a flattening convention that predates this branch. Total CSS actually shrank slightly, since DocSearch's properties are no longer duplicated by inlining.


4. UI fixes found while checking the theme in a browser

Admonitions lost their icon column

src/css/doc.css set table-layout: fixed on .doc .admonitionblock > table, inherited from the default Antora UI. That UI positions td.icon absolutely, so collapsing the icon column costs it nothing. This UI keeps the icon in flow as a vertical label bar, and under fixed layout it gets zero width because td.content is width: 100%.

Removing the one declaration is a production no-op, confirmed by diffing the compiled stylesheet rule by rule: exactly one declaration differs. The rule matches nothing on the built site anyway, since extensions/table.js wraps every table in a div.table-wrapper and breaks the child combinator. It only ever bit the preview, which did not run that postprocessor. See item 5.

Pagination links drew a full-width underline

nav.pagination span stretched its anchor to half the container. .doc a draws its underline with a repeating background-image gradient, so a stretched anchor painted that dashed line across the whole box rather than under the label. Fixed with align-items so the anchor keeps its text width.

DocSearch widget theme

The widget shipped in Algolia's default blue. It is now themed in src/css/docsearch.css, scoped to .DocSearch, which is present on both the launch button and the modal container.

Two traps are worth flagging for reviewers, both documented in the file:

  • Values are literal, not var() references. postcss-custom-properties substitutes var() in ordinary declarations but not inside custom-property declarations, and the :root palette is stripped from the output. A first version written as var(--color-camel-orange) compiled to an unresolved var() against a deleted variable, so the theme was silently dead.
  • Seven derived vendor properties are restated. DocSearch derives them at :root, so they resolve at their declaration site and overriding the inputs lower in the tree does not propagate.

Three vendor properties are deliberately not set, because the corresponding SVGs hardcode their colors in inline <style> blocks. --docsearch-logo-color was dropped rather than ship dead code, and the no-results glyph is handled with a stroke: currentcolor rule instead.

Also sized the searchbox in rem: the vendor default is a fixed 56px, which clipped descenders in the input against this site's 18px root font size. Ruled out the font swap as the cause by toggling to system-ui in the live page, where the text still measured 19px.

Four unit tests cover the theme, all mutation-checked: scoped to .DocSearch rather than :root, no var() left in custom-property declarations, searchbox height in rem, and the derived properties restated.


5. Preview and test tooling

The preview diverged from the built site

gulp preview ran plain Asciidoctor with none of the postprocessors antora-playbook-production.yml registers, so what it rendered was not what the site rendered. That is what made the admonition bug in item 4 look like a theme regression when it was a harness artifact. Measured across four environments: local preview reported table-layout: fixed with a 0px icon column, while camel.apache.org, the Netlify deploy preview and the locally built site all reported auto and 33px.

build-preview-pages.js now registers the same extensions as the playbook. @asciidoctor/tabs is deliberately left out and the reason is documented inline: the 1.0.0-beta.3 build targets the Opal runtime of asciidoctor.js 2 and throws on require under the @asciidoctor/core 3 this UI depends on. The site build is unaffected, since Antora supplies its own Asciidoctor, so tabs render as description lists in the preview only.

Example pages

preview-src gained pages for admonitions, source code, tables, tabs and asciinema, wired into the sample nav. Content is original rather than adapted from the theme that prompted it, which is MPL-2.0 and therefore ASF Category B.

Tests run from gulp

gulp test uses run() from node:test so gulp settles on the returned promise and a failing test fails the task it is sequenced into, which is bundle. Verified both ways: exit 1 with a forced failure, exit 0 when restored.

asciinema

@springio/antora-extensions/asciinema-extension is registered in the playbook, with the player assets and partials guarded on the page attribute it sets, so pages without a cast load neither the script nor the stylesheet.

The block is an Antora extension and never runs under gulp preview, so the preview reproduces it locally against the extension's own partials and helpers from node_modules. One subtlety worth knowing if this is ever touched: the cast ids are collected into a plain object rather than set on the document, mirroring what the extension does with file.asciidoc.attributes. Asciidoctor rolls back attributes assigned from the body once parsing ends, so a page- attribute set from a block processor is gone by the time getAttributes() runs.

Unused media detection

extensions/detect-unused-media.js reports unreferenced images to build/unused-media.txt. It logs at info rather than warn on purpose: this playbook sets runtime.log.failure_level: warn, so warning once per unused file would fail the whole site build over a housekeeping issue. That is why the off-the-shelf @bonitasoft/antora-detect-unused-media-extension was dropped in favor of a local one.


Verification

  • Theme build: ui-bundle.zip at 697689 bytes / 124 files, and the CSS hash is reproducible across runs
  • All 58 binary assets byte-identical to the gulp 4 baseline
  • yarn build:antora: exit 0, 4574 HTML files across all nine components, indexer output intact (camel-kafka-connector 218 pages, camel-kamelets 505)
  • The asciinema extension is confirmed to run in a real build: documentation/_/js/vendor/asciinema-player.js and css/vendor/asciinema-player.css exist in the output, and they get there only through its uiLoaded hook
  • extensions/detect-unused-media.js wrote a 30-entry report during that build and, as designed, logged no warnings, so it did not trip failure_level: warn
  • sitemap, htaccess and generate-markdown (4579 files) all pass under gulp 5
  • 22 unit tests, now run by gulp test and sequenced into bundle. They cover the custom-property plugin, the search transform and the DocSearch theme. The search suite evaluates the shipped IIFE against stubbed globals and replays captured Algolia responses, so it exercises the real file rather than a copy. Mutation-checked throughout: weakening the per-page cap, dropping to hitsPerPage: 20, removing the sort, or moving the theme to :root each fail the relevant assertions.

Checked in a browser: the themed DocSearch modal, keyboard selection and the no-results screen; admonitions across four environments (see item 5); and both asciinema players mounting and playing through in the preview.

Still to verify

  • The [asciinema] block has never been exercised in a real site build. Everything here runs against the preview reimplementation. No content source carries a cast yet, so the extension's block processor and the _asciinema/<id>.cast output remain unproven end to end.
  • The DocSearch modal on mobile, and the data-theme="dark" toggle.
  • The demo cast is a camel tui session recorded at 160x44. At the width of the docs content column that is small text, so the size may want revisiting.

🤖 Generated with Claude Code

ammachado and others added 2 commits August 13, 2026 10:36
Fixes a silent build bug and modernises both gulp pipelines.

The UI bundle was being produced empty (22 bytes, 0 files). The build task
in antora-ui-camel is an async function that returned a stream, and gulp
settles an async task on its promise; a stream is not a thenable, so
bundle:pack ran against a dest that had not been written yet. The pipeline
is now awaited explicitly. This only affected consumers of ui-bundle.zip,
since the production playbook reads the staged directory instead.

Theme moved to gulp 5, vinyl-fs 4, undertaker 2, through2 5 and fs-extra 11:

- vinyl-fs 4 is built on streamx, and merge-stream is a node-core
  PassThrough that silently drops files from streamx sources (93 files in,
  3 out). Replaced with ordered-read-streams, the streamx-native merge.
- gulp 5 decodes contents as UTF-8 by default, so binary sources now pass
  encoding: false. All 58 fonts and images verified byte-identical to the
  gulp 4 output.
- Removed the font glob in build.js. src/font has never existed; the fonts
  are copied into dest/font by the postcssUrl handler. vinyl-fs 3 ignored
  the missing directory, vinyl-fs 4 raises ENOENT.

PostCSS plugins updated to the PostCSS 8 API. cssnano 8 returns a plugin
object rather than a callable, and the bare (css, result) plugins were
PostCSS 7 style and were no longer being invoked. postcss-custom-properties
15 keeps the :root block after substitution, so resolved custom properties
are now dropped explicitly; the generated CSS is 94 bytes smaller than
before with prefixes and media queries unchanged.

Root gulpfile moved to gulp 5. Replaced del with fs.rm, as del 7+ is ESM
only, and fixed generate-markdown signalling completion twice by both
calling done() and returning a promise.

Antora 3.1.9 added a warning that fires when an extension's register
function names its first parameter 'registry'. Two of the @djencks
extensions do, and runtime.log.failure_level is set to warn, so the
advisory warning failed the build. Those packages were last published in
2022 and cannot be fixed upstream, so they are required via thin local
wrappers in extensions/. Antora now completes with no output at all.

Also removes max-width: var(--static-max-width) from .blog. The variable
was never declared, so the declaration was invalid at computed-value time
and max-width already resolved to none. This matches .static, which
constrains width only in the desktop media query.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `checks` CI job runs `yarn check:dependencies`, which failed because
21 transitive packages could be deduped using the highest strategy. The
drift is unrelated to the gulp/Antora upgrade; the lockfile simply went
stale as newer versions of caniuse-lite, nanoid, end-of-stream, fastq,
fs-extra, postcss, streamx, resolve, readable-stream and svgo published.

Ran `yarn update:dedupe`. Both `check:cache` and `check:dedupe` now exit 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado
ammachado marked this pull request as ready for review August 14, 2026 17:42
@ammachado
ammachado requested a lite review from Copilot August 14, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Updates the website build toolchain by upgrading both the root gulp pipeline and the Antora UI theme pipeline to gulp 5, upgrading Antora to 3.1.15, and adjusting related build steps (PostCSS 8 plugin API, binary asset handling, and Antora extension wrappers) to keep the build reliable and reproducible.

Changes:

  • Upgrade Antora CLI/site-generator to 3.1.15 and wrap legacy Antora extensions to avoid warn-level build failures.
  • Upgrade gulp pipelines to gulp 5 (including stream handling fixes and binary-safe asset processing).
  • Update UI theme build outputs and PostCSS pipeline (cssnano/PostCSS 8 plugin API migration) and apply a small CSS cleanup.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
package.json Bumps Antora to 3.1.15, upgrades gulp to v5, removes del.
gulpfile.js Updates task async completion patterns and replaces del with fs.rm.
gulp/tasks/generate-markdown.js Fixes async completion by removing callback usage from async task.
extensions/asciidoctor-jsonpath.js Adds wrapper to avoid Antora 3.1.9+ register-signature warning behavior.
extensions/asciidoctor-antora-indexer.js Adds wrapper to avoid Antora 3.1.9+ register-signature warning behavior.
antora-ui-camel/src/css/blog.css Removes an ineffective/invalid max-width declaration.
antora-ui-camel/public/_/rev-manifest Updates compiled asset hash mapping for new CSS output.
antora-ui-camel/public/_/partials/head-styles.hbs Updates stylesheet link to new hashed CSS filename.
antora-ui-camel/public/_/helpers/asset.js Updates embedded manifest mapping for new hashed CSS filename.
antora-ui-camel/public/_/data/rev-manifest.json Updates JSON manifest mapping for new hashed CSS filename.
antora-ui-camel/public/_/css/site-f06a797174.css Removes old compiled CSS asset.
antora-ui-camel/public/_/css/site-00920fbc4a.css Adds new compiled CSS asset with updated output.
antora-ui-camel/package.json Upgrades theme build deps for gulp 5/PostCSS 8 and raises Node engine floor.
antora-ui-camel/gulp.d/tasks/remove.js Updates through2 API usage for gulp 5 dependency stack.
antora-ui-camel/gulp.d/tasks/pack.js Ensures binary assets are packed without UTF-8 decoding under gulp 5.
antora-ui-camel/gulp.d/tasks/build.js Replaces merge-stream with ordered-read-streams; updates PostCSS plugin API; awaits pipeline completion to avoid empty bundles.
antora-ui-camel/gulp.d/tasks/build-preview-pages.js Updates stream merging + completion semantics for gulp 5 stack.
antora-playbook-snippets/antora-playbook.yml Switches extension requires to local wrappers to prevent warn-level build failures.
Files not reviewed (1)
  • antora-ui-camel/public/_/css/site-00920fbc4a.css: Generated file

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread antora-ui-camel/gulp.d/tasks/build-preview-pages.js Outdated
Comment thread antora-ui-camel/package.json
ammachado and others added 3 commits August 14, 2026 14:06
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s job

The detect-drafts job never installs dependencies, so setup-node@v5's
new package-manager-cache default (true) fails the post-step with
"Path Validation Error" since there's no cache folder to save,
failing the job despite the draft-detection logic succeeding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
cssnano 8.x (bumped from ~4.1 as part of the gulp 5 / PostCSS 8
migration) declares engines.node >=22.11, since postcss-merge-longhand
uses Set.prototype.isSubsetOf, which Node only ships unflagged from
v22. Under Node 20 this threw inside the CSS build stream; the error
was swallowed by an unhandled 'error' event on a source stream fed
into ordered-read-streams, so the task just hung until gulp-cli's
"did not complete" exit message, with no visible stack trace in CI.

Verified locally: gulp bundle, yarn build-all, and yarn checks
(link checker aside, which hardcodes a Linux binary and can't run on
macOS) all pass under Node 22.20.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

Fixes the stale compat/typescript patch-hunk warning during install
(patch hash no longer matched typescript 5.9.3 under the old Yarn).
Also tightens approvedGitRepositories to [] (no git-protocol deps in
this repo) and sets npmMinimalAgeGate to 4320 minutes (3 days) to
delay trusting newly-published npm package versions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

ammachado and others added 3 commits August 14, 2026 23:11
…handling

The DocSearch v5 migration dropped the result curation the previous Algolia
bundle performed, and routed @docsearch/css through postcss-custom-properties
with preserve: false, which flattened away its runtime cascade.

Search curation (src/js/08-docsearch.js):

* restore the sub-project exclusion via transformItems
* restore core-docs-over-components ranking; DocSearch groups by hierarchy.lvl0
  into an insertion-ordered object, so the emitted order decides group order
* cap each parent page at 2 hits. The original dedupe needed the query to spot a
  direct parent match and transformItems never receives it, so cap by page
  instead. Without this a query like "timer" fills the first group with anchors
  from a single document
* fetch 50 hits rather than the DocSearch default of 20. The index has no
  attributeForDistinct and no attributesForFaceting, so both filters run after
  Algolia has picked its window and the default starves the result list

Stylesheet delivery (gulp.d/tasks/build.js):

* give @docsearch/css its own build stream that skips the custom property pass,
  mirroring how the DocSearch UMD bundle already ships as vendor JS. This keeps
  the mobile --docsearch-spacing overrides and the data-theme=dark block live.
  Preferred over preserve: true, which costs 3.8 KB gzip site-wide and would
  reverse a convention predating this branch
* fail loudly when the @docsearch/js UMD path cannot be resolved

Custom property plugin (gulp.d/lib/drop-resolved-custom-properties.js):

* only drop definitions postcss-custom-properties actually substituted. Matching
  :root by selector string and recursing into at-rules emptied the @media block
  @docsearch/css ships, and missed resolved :root, :host list forms

Tests: 16 cases covering both the plugin and the search transform. The transform
suite evaluates the shipped IIFE against stubbed globals and replays captured
Algolia responses, so it exercises the real file rather than a copy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado
ammachado marked this pull request as draft August 15, 2026 04:51
@ammachado

ammachado commented Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Code review follow-up (d84cef1)

A review pass over the DocSearch v5 migration turned up six findings; all are addressed in d84cef15. Summarizing the two that changed behavior, since they are worth a look rather than just a rubber stamp.

Search result curation was silently dropped

The deleted src/js/vendor/algoliasearch.bundle.js filtered sub-project hits, de-duplicated hits pointing at the same parent page, and ranked core docs above component pages. The v5 config carried none of that over. Restored in adapted form:

  • Sub-project exclusion via transformItems.
  • Core-docs ranking via transformItems. This works because DocSearch groups by hierarchy.lvl0 into an insertion-ordered plain object (buildQuerySources in @docsearch/react), so the order we emit decides both group order and within-group order.
  • Per-page cap of 2 hits, replacing the old query-aware deduplicateHits. transformItems never receives the query, and the original only used it to detect a direct parent match, so capping by page reaches an equivalent outcome without it.
  • hitsPerPage: 50 instead of the DocSearch default of 20.

That last one is a real regression fix, not tuning. Both filters run client side, so they operate on an already-truncated window. Distinct parent pages surviving the sub-project filter, measured against the live index:

query hitsPerPage 20 hitsPerPage 50
kamelet 2 pages 3 pages
timer 1 page 7 pages
rest dsl 7 pages 16 pages

At the default, timer returned 19 usable hits spanning a single document, so the first result group was five near-identical anchors off one page.

Why this is worked around in the browser at all

The durable fix belongs at the index level, and there is more going on there than is obvious. Filed as CAMEL-24396. Summary of what I verified against the live index:

  • .docsearch.config.json already tries to configure both settings ("attributesForFaceting": ["version"] and "attributeForDistinctResults": "url"), but neither is in effect. Requesting {"facets": ["version"], "hitsPerPage": 0} returns "facets": {}, and {"distinct": true} is a no-op.
  • attributeForDistinctResults is not a real Algolia setting. The name is attributeForDistinct, and it must be paired with distinct, which is absent. Per Algolia's API reference: "If attributeForDistinct isn't set, distinct is ignored."
  • The file matches neither documented DocSearch config format. The current DocSearch/Crawler config is JavaScript (new Crawler({ startUrls, sitemaps, actions, initialIndexSettings })), not JSON nested under index / crawler / custom_settings. Nothing in this repo references the file outside README.md and .docsearch.README.md; there is no CI job or script that applies it.
  • The records lack the attribute the fix would normally use. Retrieving all attributes returns only content, hierarchy, keywords, objectID, pageRank, url, version. There is no url_without_anchor, anchor, type or weight, so this index is not built by DocSearch's standard helpers.docsearch() extractor. Since url includes the #fragment, attributeForDistinct: "url" would group by anchor and de-duplicate nothing.

So the client-side workaround is currently the only lever available, and the ticket's first step is confirming whether .docsearch.config.json feeds the crawler at all or whether the live config lives only in the Algolia dashboard.

One thing worth watching during review: DocSearch v5 uses item.type === 'lvl1' to nest sub-results under their parent heading. With no type attribute on these records, that grouping cannot engage. Search works, since hierarchy, url and content are all present, but the result list will not render DocSearch's usual parent/child structure.

@docsearch/css lost its runtime cascade

The vendor stylesheet was going through postcss-custom-properties with preserve: false, which flattened it. Verified in the built output: the @media (width<=768px) :root overrides were inlined at desktop values, --shimmer-bg was dead, and the 2.5 KB :root[data-theme=dark] block still shipped but could never take effect.

@docsearch/css now gets its own build stream that skips the custom-property pass, revved to css/vendor/docsearch.css and linked ahead of site.css in both the Antora and Hugo head partials. This mirrors how the DocSearch UMD bundle already ships as vendor JS.

I rejected the one-line preserve: true alternative deliberately: it fixes the same bug but costs about 3.8 KB gzip site-wide and would reverse a flattening convention that predates this branch. Total CSS actually shrank slightly, since DocSearch's properties are no longer duplicated by inlining.

Also fixed

  • The new drop-resolved-custom-properties plugin was deleting @media-nested :root definitions that postcss-custom-properties never substitutes (exactly the block @docsearch/css ships), while missing resolved :root, :host list forms.
  • The @docsearch/js UMD path now fails loudly instead of surfacing as an opaque vinyl-fs glob error.
  • Added a window.docsearch guard so a vendor-bundle load failure cannot take down the rest of the site.js listener chain.

Tests

16 cases, all passing. The search-transform suite evaluates the shipped IIFE against stubbed globals and replays captured Algolia responses, so it covers the real file rather than a copy. Mutation-checked: weakening the per-page cap, dropping to hitsPerPage: 20, or removing the sort each fails the relevant assertions.

Still to verify

Nothing has been exercised in a browser yet. Keeping this as a draft until the DocSearch modal on mobile, the data-theme="dark" toggle, and the timer / kamelet result lists are checked against a running site.


Edited to correct the index-level section: an earlier revision stated the fix could not be done from this repository, which was wrong. .docsearch.config.json exists; the accurate finding is that its settings are not in effect and one of them is misspelled.

@ammachado ammachado changed the title Build: upgrade to gulp 5 and Antora 3.1.15 Build: upgrade gulp 5 / Antora 3.1.15 / Yarn 4.18 and migrate search to DocSearch v5 Aug 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

Both found by driving the deployed preview at pr-1729--camel.netlify.app.

DocSearch renders its modal footer as a bare <footer> element, so the global
`footer` rule in footer.css applied to it. DocSearch sets `height` but never
`min-height`, so our `min-height: var(--footer-height)` won outright and
stretched the modal footer to 23rem (391px) against an expected 52px on desktop
and 48px on mobile. On mobile the modal is full screen, so the footer took 391px
of 732px, over half the viewport. Scoped the rule to `body > footer`, which also
stops the `color` and `font-size` from leaking; verified the site footer is a
classless direct child of body on the Antora and Hugo page types.

The DocSearch button is a fixed 38px, and `.navbar-search` was a block container
relying on padding alone to position it. In the 73px desktop bar that left the
button centred at 28px against 36.5px for the nav links. Made the container flex
with `align-items: center`, which brings it to 36.5px exactly. `text-align:
right` no longer applies to a flex container, so it is replaced with
`justify-content: flex-end`. Mobile is unchanged: the 55px container is exactly
38px plus padding, so padding alone already centred it there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

Search results rendered with an empty title, collapsed into a single grey
breadcrumb line. DocSearch derives the title attribute as
`hierarchy.${item.type}`, and this index is not built by DocSearch's standard
record extractor, so its records carry no `type`. The expression became the
literal 'hierarchy.undefined' and resolved to nothing.

transformItems now assigns the deepest populated hierarchy level as `type`, so
a hit on #_component_option_checkCrcs titles as "checkCrcs" with
"Camel Components > Components > Kafka" as subtext. Verified on the deployed
preview by re-initialising DocSearch with the patched config. This is a
workaround; the records should carry `type` and `anchor` - see CAMEL-24396.

Raised hitsPerPage from 50 to 75. Measured against the live index at the
current cap, this lifts "timer" from 5 to 8 distinct pages and "aggregate" from
9 to 13. 100 adds almost nothing beyond 75 (only "rest dsl", 8 to 9 pages) but
doubles the response from ~18 KB to ~36 KB gzipped.

Left MAX_HITS_PER_PAGE at 2. Raising it is counterproductive: DocSearch caps
each lvl0 group at 5 results, so a larger per-page cap spends those slots on
repeats from one document and yields fewer distinct pages, not more ("timer"
drops from 5 pages to 4 at a cap of 3).

Note that thin result sets for "Kafka" and "file" are not addressable here: 98
of the top 100 hits for "Kafka" are anchors on kafka-component.html, so no
client-side window size recovers pages that Algolia never returns. That needs
server-side distinct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

ammachado and others added 7 commits August 15, 2026 17:37
Restyle the DocSearch v5 widget to match the site palette. Camel orange
takes the accents the vendor stylesheet renders in Algolia blue, group
headings pick up the ASF dark blue used for page headings, and the
launch button matches the navbar buttons.

The overrides are declared on .DocSearch rather than :root, and with
literal values rather than var(--color-*), because the build resolves
and then strips every :root custom property from site.css. Both are
silent failure modes, so they are covered by tests.

Also fixes the query text clipping in the search box. The vendor fixes
the box at 56px while sizing its padding and font in rem, and this
site's root font-size is 18px rather than the 16px DocSearch assumes,
which left a 19px content box around a 25.2px line. Sizing the box in
rem keeps it in step with the text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The declaration is inherited from the default Antora UI, which positions
td.icon absolutely and so loses nothing when the icon column collapses.
This UI keeps the icon in flow as a vertical label bar, which needs the
column to have width; under fixed layout it gets none, because td.content
is width: 100%.

It has never taken effect in production: extensions/table.js wraps every
table in a div.table-wrapper, so the table is a grandchild of
.admonitionblock and the child combinator never matches. Removing it is
a no-op for the built site (verified: the only change to the compiled
stylesheet is this one declaration) and it fixes gulp preview, which
renders with plain Asciidoctor and no Antora extensions.

It also removes a landmine: any change to extensions/table.js would
otherwise collapse every admonition on the site.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…extensions

The preview rendered with plain Asciidoctor while the site build registers
postprocessors through antora-playbook-production.yml, so the two disagreed.
table.js is the one that matters: it wraps every table in a div.table-wrapper,
and without it the preview collapsed the icon column of every admonition.
Register table.js and inline-styles.js so preview markup matches the built site.

@asciidoctor/tabs is registered by the playbook but deliberately left out: the
1.0.0-beta.3 build targets the Opal runtime of asciidoctor.js 2 and throws on
require under the @asciidoctor/core 3 this UI depends on. The site build is
unaffected, since Antora supplies its own Asciidoctor.

Add three example pages covering the parts of the UI that needed a closer look:
admonitions, source highlighting, and tables. Content is original and
Camel-flavoured rather than lorem ipsum, so it exercises realistic material.

The admonitions page documents one finding: the label bar is sized by the
table's auto layout, so it widens from 33px to 63px or 137px once the block
sits inside a list item or a table cell. No page on the site nests an
admonition that way today.

No change to the shipped UI bundle; preview-src is not packaged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…heck

Move the unit tests into a gulp task. gulp.d/tasks/test.js drives node:test
through its run() API rather than spawning `node --test`, so gulp settles on the
returned promise and a failure fails the task it is sequenced into. The task is
now part of the bundle series (clean, lint, test, build, pack), and the package
scripts delegate to it, so `yarn build` no longer runs the tests separately.

Register @springio/antora-extensions/asciinema-extension. It supplies the
[asciinema] block, injects asciinema-player's JS and CSS into the UI catalog,
and adds the asciinema-styles, asciinema-load-scripts and
asciinema-create-scripts partials. asciinema-player arrives as a transitive
dependency, so it needs no entry of its own.

Replace @bonitasoft/antora-detect-unused-media-extension with a local
equivalent. That extension logs one warning per unused file and this playbook
sets runtime.log.failure_level to warn, so a single stray image would have
failed the site build. extensions/detect-unused-media.js logs at info instead
and writes build/unused-media.txt, keeping the report without the failure. It
still ignores .cast by default so asciinema recordings are not reported.

Add preview pages for tabs and asciinema. Neither renders in gulp preview:
@asciidoctor/tabs 1.0.0-beta.3 targets the Opal runtime of asciidoctor.js 2 and
cannot load under the @asciidoctor/core 3 the UI depends on, and the asciinema
block comes from an Antora extension the standalone preview does not run. Both
pages say so.

Also drops a committed vim swap file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The prev/next links drew a dashed rule across the full half-width of the
footer. .doc a paints its underline with a repeating background-image gradient,
and nav.pagination span is a column flex container, so the anchor stretched to
the container width and the gradient painted all of it. align-items keeps the
anchor at its text width; the links now sit in bordered boxes.

Reference the asciinema partials from head-styles.hbs and footer-scripts.hbs,
guarded on the page-asciinemacasts attribute the extension sets. The guard
keeps the player assets off pages with no recording, and keeps gulp preview
from failing on partials that only exist during an Antora build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Superseded by extensions/detect-unused-media.js, which reports the same thing
at info level so it cannot trip runtime.log.failure_level: warn.

Updates the two places that still named the package.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The [asciinema] block comes from @springio/antora-extensions, an Antora
extension, so it never ran under gulp preview: the blocks stayed literal
and the page could not be checked without a full site build.

Reproduce the parts of it the preview needs, reusing the extension's own
partials, helpers and player assets from node_modules rather than copying
them: an image$ include processor so preview pages use the same include
syntax a real component does, the asciinema block with the same md5-derived
id and _asciinema/<id>.cast layout, and registration of the partials the UI
templates already guard on.

The cast ids are collected into a plain object instead of being set on the
document, mirroring what the extension does with file.asciidoc.attributes.
Asciidoctor rolls back attributes assigned from the body once parsing ends,
so a page- attribute set from the block processor is gone by the time
getAttributes() runs.

Add a recorded camel-jbang TUI session as the example cast.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The recorder decoded each read() chunk on its own, so a multi-byte sequence
split across a chunk boundary became U+FFFD. Sixty of them survived into the
committed cast, and each one costs a column: a 2-wide emoji rendered as a
1-wide replacement box, so every row containing one drifted left and the next
redraw left stale characters behind. The visible symptom was a ghost row under
the tab bar.

Re-recorded with an incremental decoder, which brings it to zero replacement
characters and a clean header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ammachado ammachado changed the title Build: upgrade gulp 5 / Antora 3.1.15 / Yarn 4.18 and migrate search to DocSearch v5 Build: gulp 5 / Antora 3.1.15 / Yarn 4.18, DocSearch v5, and UI preview tooling Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Preview is available at https://pr-1729--camel.netlify.app

@ammachado
ammachado marked this pull request as ready for review August 16, 2026 03:19
@ammachado
ammachado requested a balanced review from Copilot August 16, 2026 03:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 67 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • antora-ui-camel/public/_/css/site-74543f5b13.css: Generated file
Suppressed comments (2)

antora-ui-camel/src/css/footer.css:4

  • Scoping only this rule does not fully isolate the site footer. The later @media (width <= 1024px) { footer { flex-direction: column; } } still matches DocSearch's <footer class="DocSearch-Footer">, changing its action/logo row into a column on tablet and mobile. Scope that media-query selector to body > footer as well.
    extensions/detect-unused-media.js:42
  • Returning here leaves an old build/unused-media.txt untouched. After a build that reports unused files, a later clean result still exposes the stale list and can lead maintainers to delete assets that are now referenced. Remove or truncate the report when unused is empty before returning.

},
"engines": {
"node": ">= 8.0.0"
"node": ">= 18.0.0"
Comment on lines +54 to +57
// NOTE the same shapes the crawler-independent extension used: image:target[] and image::target[]
// for images, video::target[] for video. Anything the reference cannot be resolved from, such as a
// target built from an attribute, is simply not matched, so this under-reports rather than
// reporting a used file as unused.
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.

2 participants