From 6ccad459318d0792f2ba143e8b25a1c2013d6410 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Sat, 8 Aug 2026 12:48:14 +0100 Subject: [PATCH 1/4] feat: make property tooltips opt-in via prop macro markers Property tooltips previously decorated every inline code element whose text matched a property name, so ambiguous words such as admin, brokers, rack, retries, and superusers picked up tooltips in unrelated contexts (the admin listener in Helm values, audit-logging settings). Tooltips now decorate only elements emitted by the prop: AsciiDoc macro (class property-ref plus data-property-name), which validates names against the published property JSON at build time. The lookup name comes from data-property-name, so display text overrides keep working. The test page simulates macro output with pass-through HTML and asserts the old false-positive cases stay undecorated. --- preview-src/property-tooltips-test.adoc | 38 ++++++++++++++++--------- src/js/19-property-tooltips.js | 23 ++++++++------- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/preview-src/property-tooltips-test.adoc b/preview-src/property-tooltips-test.adoc index 9fd7df5c..9c0c5d09 100644 --- a/preview-src/property-tooltips-test.adoc +++ b/preview-src/property-tooltips-test.adoc @@ -1,29 +1,29 @@ = Property Tooltips Test :page-layout: default -Test page for Redpanda configuration property tooltips. Property tooltips are enabled by default on all pages. To disable on a specific page, use `:page-disable-property-tooltips: true`. +Test page for Redpanda configuration property tooltips. Tooltips are opt-in: only code elements emitted by the `prop:` AsciiDoc macro (class `property-ref` plus a `data-property-name` attribute) are decorated. This page simulates the macro's output with pass-through HTML because the UI preview doesn't run the docs macros. To disable tooltips on a specific page, use `:page-disable-property-tooltips: true`. TIP: Hover over property names to see documentation tooltips! == Cluster Properties in Paragraphs -When configuring Redpanda, you may need to adjust `log_segment_size` for optimal storage performance. The default segment size works for most workloads, but large messages may benefit from larger segments. +When configuring Redpanda, you may need to adjust +++log_segment_size+++ for optimal storage performance. The default segment size works for most workloads, but large messages may benefit from larger segments. -To enable authentication, set `enable_sasl` to true. This requires clients to authenticate using SASL mechanisms. +To enable authentication, set +++enable_sasl+++ to true. This requires clients to authenticate using SASL mechanisms. == Topic Properties in Lists Topic-level configuration properties: -* `retention_bytes` - Controls maximum partition size before deletion +* +++retention_bytes+++ - Controls maximum partition size before deletion * This is a topic-scoped property that can override cluster defaults == Dotted Property Names (Topic-scoped) These properties use dot notation and test the anchor slugification fix: -* `redpanda.storage.mode` - Controls storage mode (local vs tiered) -* `redpanda.remote.read` - Enables remote reads from object storage +* +++redpanda.storage.mode+++ - Controls storage mode (local vs tiered) +* +++redpanda.remote.read+++ - Enables remote reads from object storage The "View full documentation" link for `redpanda.storage.mode` should generate an anchor like `#redpanda-storage-mode` (dots replaced with hyphens), not `#redpanda.storage.mode`. @@ -31,15 +31,31 @@ The "View full documentation" link for `redpanda.storage.mode` should generate a Some properties are enterprise-only: -* `cloud_storage_enabled` - Requires Enterprise license (self-hosted only) +* +++cloud_storage_enabled+++ - Requires Enterprise license (self-hosted only) == Broker Properties -Per-broker settings like `kafka_connections_max` control resource limits. +Per-broker settings like +++kafka_connections_max+++ control resource limits. == Deprecated Properties -The property `group_max_session_timeout_ms` is deprecated but still shown with appropriate styling. +The property +++group_max_session_timeout_ms+++ is deprecated but still shown with appropriate styling. + +== Display text override + +The macro's text attribute changes the display while keeping the lookup name: +++object storage flag+++ should show the Tiered Storage property tooltip. + +== Unmarked property names get NO tooltip (the old false-positive case) + +These are real property names in plain backticks, exactly what used to be auto-matched. With opt-in marking they must NOT have tooltips: + +* `log_segment_size` - No tooltip (not marked) +* `enable_sasl` - No tooltip (not marked) +* The `admin` listener in a Helm values file - No tooltip (ambiguous word) + +== Marked but unknown property + +A marked element whose name is not in the published data renders untouched: +++this_is_not_a_property+++ - No tooltip. == Properties in Code Blocks @@ -57,7 +73,3 @@ Regular code elements should NOT have tooltips: * `this_is_not_a_property` - No tooltip * `some_random_function()` - No tooltip * `myVariable` - No tooltip - -== Mixed Content - -In a paragraph with both properties and regular text: Set `enable_sasl` to configure authentication, then use `kubectl apply` to deploy. Only `enable_sasl` should have a tooltip. diff --git a/src/js/19-property-tooltips.js b/src/js/19-property-tooltips.js index 7efab8b0..6b5f1a83 100644 --- a/src/js/19-property-tooltips.js +++ b/src/js/19-property-tooltips.js @@ -2,8 +2,12 @@ /** * Redpanda Property Tooltips * - * Adds hover documentation tooltips to configuration property names. - * Enabled by default on all pages. Disable on specific pages with: + * Adds hover documentation tooltips to configuration property references. + * Marking is opt-in: only code elements emitted by the prop: AsciiDoc macro + * (class property-ref plus a data-property-name attribute) are decorated. + * Plain backticked words are never matched, so ambiguous terms such as + * admin or rack in Helm or feature contexts don't pick up wrong tooltips. + * Disable on specific pages with: * :page-disable-property-tooltips: true */ @@ -363,14 +367,13 @@ return } - // Create a Set for fast lookup - var propertyNames = new Set(Object.keys(properties)) - - // Scope: opt-in pages look at all elements in the article + // Scope: only elements marked by the prop: macro are decorated var article = document.querySelector('article.doc') if (!article) return - var codeElements = article.querySelectorAll('code:not(.has-property-tooltip)') + var codeElements = article.querySelectorAll( + 'code[data-property-name]:not(.has-property-tooltip), code.property-ref:not(.has-property-tooltip)' + ) var isTouch = isTouchDevice() var getTippyConfig = function (content) { @@ -388,10 +391,10 @@ } codeElements.forEach(function (codeEl) { - var text = codeEl.textContent.trim() + var text = codeEl.getAttribute('data-property-name') || codeEl.textContent.trim() - // Check if this code element matches a property name - if (propertyNames.has(text)) { + // Look up the marked property in the published data + if (Object.prototype.hasOwnProperty.call(properties, text)) { var prop = properties[text] var tooltipContent = createPropertyTooltip(prop) From 6ed53d6b9ff4677b0bc7d8ed98af6de6a6f3aa33 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Sat, 8 Aug 2026 13:01:58 +0100 Subject: [PATCH 2/4] feat: link property tooltips to the current component's own property pages Config properties are published in several components (streaming, cloud, and sometimes agentic-data-plane and connect), but the tooltip's View full documentation link hardcoded streaming's URL space, so cloud pages linked back into streaming. head-meta now emits a properties-pages-url meta tag resolved through resolve-resource WITHOUT an explicit component, so it resolves reference:properties/cluster-properties.adoc in the current page's own component and version. The tooltip swaps the property's scope into that URL. Components without property pages omit the tag (and unresolved preview placeholders are ignored), falling back to the previous streaming-relative behavior. --- src/js/19-property-tooltips.js | 28 +++++++++++++++++++++++++--- src/partials/head-meta.hbs | 10 ++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/js/19-property-tooltips.js b/src/js/19-property-tooltips.js index 6b5f1a83..b1fffaad 100644 --- a/src/js/19-property-tooltips.js +++ b/src/js/19-property-tooltips.js @@ -38,6 +38,21 @@ return null } + /** + * Get the component-local property pages base URL from meta tag. + * head-meta resolves reference:properties/cluster-properties.adoc in the + * current page's own component, so cloud pages link to cloud's property + * pages, streaming pages to streaming's, and so on. + */ + function getPropertiesPagesUrl () { + var meta = document.querySelector('meta[name="properties-pages-url"]') + // Ignore unresolved placeholders (the UI preview resolver emits '#'). + if (meta && meta.content && meta.content.indexOf('cluster-properties') !== -1) { + return meta.content + } + return null + } + /** * Get the latest Redpanda tag from meta tag (for cache versioning) */ @@ -280,14 +295,21 @@ parts.push('
Range: ' + range.join(', ') + '
') } - // Link to full documentation (use current page version) + // Link to full documentation, relative to the current page's component var scope = prop.configScope || 'cluster' - var version = getDocVersion() // AsciiDoc auto-ID generation: dots are removed, underscores become hyphens // e.g., "redpanda.storage.mode" -> "redpandastoragemode" // e.g., "log_retention_ms" -> "log-retention-ms" var anchor = prop.name.replace(/\./g, '').replace(/_/g, '-') - var docUrl = '/' + version + '/reference/properties/' + scope + '-properties/#' + anchor + var pagesUrl = getPropertiesPagesUrl() + var docUrl + if (pagesUrl) { + // Swap the scope into the component-resolved cluster-properties URL. + docUrl = pagesUrl.replace('cluster-properties', scope + '-properties') + '#' + anchor + } else { + // Fallback for pages without the meta tag: streaming URL space. + docUrl = '/' + getDocVersion() + '/reference/properties/' + scope + '-properties/#' + anchor + } parts.push('View full documentation →') return '
' + parts.join('') + '
' diff --git a/src/partials/head-meta.hbs b/src/partials/head-meta.hbs index 3193ee8d..76b8583e 100644 --- a/src/partials/head-meta.hbs +++ b/src/partials/head-meta.hbs @@ -42,6 +42,16 @@ {{/if}} {{/with}} {{/if}} +{{!-- + Component-local property pages base for tooltip doc links. Resolved in the + current page's own component (resolve-resource defaults to the page's + component and version), so cloud pages link to cloud's property pages and + streaming pages to streaming's. Omitted when the component has no property + pages; the JS then falls back to streaming's URL space. +--}} +{{#with (resolve-resource 'reference:properties/cluster-properties.adoc' fallback='')}} + +{{/with}} {{!-- Connect JSON URL for Bloblang tooltips From 00e5e37ebb3f0ad8ab31abeb44916f48bc9d051f Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Sat, 8 Aug 2026 20:21:11 +0100 Subject: [PATCH 3/4] feat: render prop and config_ref macro calls in tooltip descriptions Generated property descriptions can contain prop macro calls (the property extractor now emits them for cross-property references), and older published JSONs still carry legacy config_ref calls. Both previously showed as raw macro text inside tooltips. The description formatter now renders them as code, honoring the prop macro's text= display override and config_ref's payload. --- preview-src/property-tooltips-test.adoc | 4 ++++ src/js/19-property-tooltips.js | 15 ++++++++++++++- src/static/redpanda-properties.json | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/preview-src/property-tooltips-test.adoc b/preview-src/property-tooltips-test.adoc index 9c0c5d09..3f0d3374 100644 --- a/preview-src/property-tooltips-test.adoc +++ b/preview-src/property-tooltips-test.adoc @@ -37,6 +37,10 @@ Some properties are enterprise-only: Per-broker settings like +++kafka_connections_max+++ control resource limits. +== Macro Calls Inside Tooltip Descriptions + +The +++group_max_session_timeout_ms+++ tooltip description contains a `prop:` macro call and a legacy `config_ref:` call. Both must render as plain code in the tooltip, never as raw macro text. + == Deprecated Properties The property +++group_max_session_timeout_ms+++ is deprecated but still shown with appropriate styling. diff --git a/src/js/19-property-tooltips.js b/src/js/19-property-tooltips.js index b1fffaad..dc84f36d 100644 --- a/src/js/19-property-tooltips.js +++ b/src/js/19-property-tooltips.js @@ -355,8 +355,21 @@ // Convert backticks to code tags var withCode = escaped.replace(/`([^`]+)`/g, '$1') + // Render prop macro calls from generated descriptions as code (the + // text= attribute wins as the display, matching the macro's rendering) + var withProps = withCode.replace(/prop:([^[\s]+)\[([^\]]*)\]/g, function (match, name, attrs) { + var textMatch = attrs.match(/text=([^,\]]+)/) + return '' + (textMatch ? textMatch[1] : name) + '' + }) + + // Legacy config_ref macro calls survive in older published JSONs + withProps = withProps.replace(/config_ref:([^[,]+)(?:,[^[]*)?\[([^\]]*)\]/g, function (match, name, payload) { + var display = payload.replace(/^`|`$/g, '') || name + return '' + display + '' + }) + // Fallback: resolve any remaining xrefs that weren't pre-resolved - var withXrefs = withCode.replace( + var withXrefs = withProps.replace( /xref:\.?\/?([^[]+)\.adoc(?:#([^[]*))?\[([^\]]+)\]/g, function (match, path, anchor, display) { var href = path.replace(/^\.\//, '') + '/' diff --git a/src/static/redpanda-properties.json b/src/static/redpanda-properties.json index 71dba70f..bb9ccbfa 100644 --- a/src/static/redpanda-properties.json +++ b/src/static/redpanda-properties.json @@ -68,7 +68,7 @@ "name": "group_max_session_timeout_ms", "type": "integer", "default": 300000, - "description": "The maximum session timeout for consumer groups. If a consumer does not send a heartbeat within this time, it is considered dead and removed from the group.", + "description": "The maximum session timeout for consumer groups. If a consumer does not send a heartbeat within this time, it is considered dead and removed from the group. Works together with prop:log_segment_size[link=true] and the legacy config_ref:enable_sasl,true,cluster-properties[`enable_sasl`] form renders too.", "config_scope": "cluster", "needs_restart": false, "cloud_supported": true, @@ -102,4 +102,4 @@ "visibility": "user" } } -} +} \ No newline at end of file From 9159c842e333f45e46e088e1b0f3ef02b0c4083d Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Mon, 10 Aug 2026 08:35:09 +0100 Subject: [PATCH 4/4] fix: treat resolve-resource calls with an explicit fallback as optional The properties-pages-url meta resolves a property reference page in the current page's own component, which legitimately does not exist for most components (home, labs, connect, search, and older versions with the flat layout). Those misses produced eight unresolved-resource warnings per build. A caller that passes fallback= (even an empty one) declares the resolution optional and handles the miss itself, so the helper no longer logs for it. --- src/helpers/resolve-resource.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/helpers/resolve-resource.js b/src/helpers/resolve-resource.js index 018ecf1f..b7c3fb92 100644 --- a/src/helpers/resolve-resource.js +++ b/src/helpers/resolve-resource.js @@ -42,12 +42,16 @@ function logUnresolved (resource, reason, page, context, logger) { module.exports = (resource, { data, hash: context }) => { const { page, logger } = data.root || {} const fallbackUrl = context?.fallback + // An explicitly provided fallback (even an empty one) marks the resolution + // as optional: the caller handles the miss, so an unresolved target is + // expected on components that don't publish the resource and must not warn. + const optional = context ? 'fallback' in context : false // Log and return undefined if resource is not provided if (!resource || typeof resource !== 'string') { // Only log if we have page context (not during initial template compilation) if (page && resource === undefined) { - logUnresolved('undefined', 'attribute not defined (check page attributes)', page, context, logger) + if (!optional) logUnresolved('undefined', 'attribute not defined (check page attributes)', page, context, logger) } return fallbackUrl || undefined } @@ -141,7 +145,7 @@ module.exports = (resource, { data, hash: context }) => { result = fallbackUrl } else { // Log warning for unresolved resource (only if no fallback provided) - logUnresolved(resolvedResource, 'target not found in content catalog', page, context, logger) + if (!optional) logUnresolved(resolvedResource, 'target not found in content catalog', page, context, logger) result = resource }