diff --git a/docs/_snippets/applies_to-version.md b/docs/_snippets/applies_to-version.md
index f98f758d0..ef6ed62be 100644
--- a/docs/_snippets/applies_to-version.md
+++ b/docs/_snippets/applies_to-version.md
@@ -1,10 +1,65 @@
`applies_to` accepts the following version formats:
-* `Major.Minor`
-* `Major.Minor.Patch`
+### Version specifiers
-Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor.Patch` format.
+You can use version specifiers to precisely control how versions are interpreted:
+
+| Specifier | Syntax | Description | Example |
+|-----------|--------|-------------|---------|
+| Greater than or equal (default) | `x.x` `x.x+` `x.x.x` `x.x.x+` | Feature available from this version onwards | `ga 9.2+` or `ga 9.2` |
+| Range (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | Feature available only in this version range | `beta 9.0-9.1` |
+| Exact version | `=x.x` `=x.x.x` | Feature available only in this specific version | `preview =9.0` |
+
+Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor` format in badges.
+
+:::{note}
+The `+` suffix is optional for greater-than-or-equal syntax. Both `ga 9.2` and `ga 9.2+` have the same meaning.
+:::
+
+### Examples
+
+```yaml
+# Greater than or equal (feature available from 9.2 onwards)
+stack: ga 9.2
+stack: ga 9.2+
+
+# Range (feature was in beta from 9.0 to 9.1, then became GA)
+stack: ga 9.2+, beta 9.0-9.1
+
+# Exact version (feature was in preview only in 9.0)
+stack: ga 9.1+, preview =9.0
+```
+
+### Implicit version inference for multiple lifecycles {#implicit-version-inference}
+
+When you specify multiple lifecycles with simple versions (without explicit specifiers), the system automatically infers the version ranges:
+
+**Input:**
+```yaml
+stack: preview 9.0, alpha 9.1, beta 9.2, ga 9.4
+```
+
+**Interpreted as:**
+```yaml
+stack: preview =9.0, alpha =9.1, beta 9.2-9.3, ga 9.4+
+```
+
+The inference rules are:
+1. **Consecutive versions**: If a lifecycle is immediately followed by another in the next minor version, it's treated as an **exact version** (`=x.x`).
+2. **Non-consecutive versions**: If there's a gap between one lifecycle's version and the next lifecycle's version, it becomes a **range** from the start version to one version before the next lifecycle.
+3. **Last lifecycle**: The highest versioned lifecycle is always treated as **greater-than-or-equal** (`x.x+`).
+
+This makes it easy to document features that evolve through multiple lifecycle stages. For example, a feature that goes through preview → beta → GA can be written simply as:
+
+```yaml
+stack: preview 9.0, beta 9.1, ga 9.3
+```
+
+Which is automatically interpreted as:
+```yaml
+stack: preview =9.0, beta 9.1-9.2, ga 9.3+
+```
:::{note}
-**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 8.18.6, ga 9.1.2, ga 8.19.2, ga 9.0.6` will be displayed as `stack: ga 9.1.2, ga 9.0.6, ga 8.19.2, ga 8.18.6`. Items without versions (like `ga` without a version or `all`) are sorted last.
+**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 9.1, beta 9.0, preview 8.18` will be displayed with the highest priority lifecycle and version first. Items without versions are sorted last.
:::
\ No newline at end of file
diff --git a/docs/syntax/_snippets/inline-level-applies-examples.md b/docs/syntax/_snippets/inline-level-applies-examples.md
index 58f38476c..1fedf4759 100644
--- a/docs/syntax/_snippets/inline-level-applies-examples.md
+++ b/docs/syntax/_snippets/inline-level-applies-examples.md
@@ -55,7 +55,7 @@ This example shows how to use directly a key from the second level of the `appli
::::{tab-item} Output
- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
-- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
+- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`
::::
@@ -63,7 +63,7 @@ This example shows how to use directly a key from the second level of the `appli
::::{tab-item} Markdown
```markdown
- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
-- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
+- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`
```
::::
diff --git a/docs/syntax/_snippets/multiple-lifecycle-states.md b/docs/syntax/_snippets/multiple-lifecycle-states.md
index bb0bedf40..8c9dcd069 100644
--- a/docs/syntax/_snippets/multiple-lifecycle-states.md
+++ b/docs/syntax/_snippets/multiple-lifecycle-states.md
@@ -1,12 +1,35 @@
-`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions. For example:
+`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions.
-* A feature is added in 9.1 as tech preview and becomes GA in 9.4:
+When you specify multiple lifecycles with simple versions, the system automatically infers whether each version represents an exact version, a range, or an open-ended range. Refer to [Implicit version inference](/_snippets/applies_to-version.md#implicit-version-inference) for details.
+
+### Examples
+
+* A feature is added in 9.0 as tech preview and becomes GA in 9.1:
+
+ ```yml
+ applies_to:
+ stack: preview 9.0, ga 9.1
+ ```
+
+ The preview is automatically interpreted as `=9.0` (exact), and GA as `9.1+` (open-ended).
+
+* A feature goes through multiple stages before becoming GA:
+
+ ```yml
+ applies_to:
+ stack: preview 9.0, beta 9.1, ga 9.3
+ ```
+
+ Interpreted as: `preview =9.0`, `beta 9.1-9.2`, `ga 9.3+`
+
+* A feature is unavailable for one version, beta for another, preview for a range, then GA:
```yml
applies_to:
- stack: preview 9.1, ga 9.4
+ stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4
```
+ Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3`, `ga 9.4+`
* A feature is deprecated in ECE 4.0 and is removed in 4.8. At the same time, it has already been removed in {{ech}}:
@@ -15,4 +38,17 @@
deployment:
ece: deprecated 4.0, removed 4.8
ess: removed
+ ```
+
+ The deprecated lifecycle is interpreted as `4.0-4.7` (range until removal).
+
+* Use explicit specifiers when you need precise control:
+
+ ```yml
+ applies_to:
+ # Explicit exact version
+ stack: preview =9.0, ga 9.1+
+
+ # Explicit range
+ stack: beta 9.0-9.1, ga 9.2+
```
\ No newline at end of file
diff --git a/docs/syntax/_snippets/versioned-lifecycle.md b/docs/syntax/_snippets/versioned-lifecycle.md
index ae6af6fee..cfe372e69 100644
--- a/docs/syntax/_snippets/versioned-lifecycle.md
+++ b/docs/syntax/_snippets/versioned-lifecycle.md
@@ -7,6 +7,8 @@
---
```
+ This means the feature is available from version 9.3 onwards (equivalent to `ga 9.3+`).
+
* When a change is introduced as preview or beta, use `preview` or `beta` as value for the corresponding key within the `applies_to`:
```
@@ -16,6 +18,28 @@
---
```
+* When a feature is available only in a specific version range, use the range syntax:
+
+ ```
+ ---
+ applies_to:
+ stack: beta 9.0-9.1, ga 9.2
+ ---
+ ```
+
+ This means the feature was in beta from 9.0 to 9.1, then became GA in 9.2+.
+
+* When a feature was in a specific lifecycle for exactly one version, use the exact syntax:
+
+ ```
+ ---
+ applies_to:
+ stack: preview =9.0, ga 9.1
+ ---
+ ```
+
+ This means the feature was in preview only in 9.0, then became GA in 9.1+.
+
* When a change introduces a deprecation, use `deprecated` as value for the corresponding key within the `applies_to`:
```
@@ -33,4 +57,6 @@
applies_to:
stack: deprecated 9.1, removed 9.4
---
- ```
\ No newline at end of file
+ ```
+
+ With the implicit version inference, this is interpreted as `deprecated 9.1-9.3, removed 9.4+`.
\ No newline at end of file
diff --git a/docs/syntax/applies.md b/docs/syntax/applies.md
index c6621e483..a1b990865 100644
--- a/docs/syntax/applies.md
+++ b/docs/syntax/applies.md
@@ -29,6 +29,41 @@ Where:
- The lifecycle is mandatory.
- The version is optional.
+### Version Syntax
+
+Versions can be specified using several formats to indicate different applicability scenarios:
+
+| Description | Syntax | Example | Badge Display |
+|:------------|:-------|:--------|:--------------|
+| **Greater than or equal to** (default) | `x.x+` `x.x` `x.x.x+` `x.x.x` | `ga 9.1` or `ga 9.1+` | `9.1+` |
+| **Range** (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | `preview 9.0-9.2` | `9.0-9.2` or `9.0+`* |
+| **Exact version** | `=x.x` `=x.x.x` | `beta =9.1` | `9.1` |
+
+\* Range display depends on release status of the second version.
+
+**Important notes:**
+
+- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of whether you specify patch versions in the source.
+- Each version statement corresponds to the **latest patch** of the specified minor version (e.g., `9.1` represents 9.1.0, 9.1.1, 9.1.6, etc.).
+- When critical patch-level differences exist, use plain text descriptions alongside the badge rather than specifying patch versions.
+
+### Version Validation Rules
+
+The build process enforces the following validation rules:
+
+- **One version per lifecycle**: Each lifecycle (GA, Preview, Beta, etc.) can only have one version declaration.
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2, ga 9.3`
+- **One "greater than" per key**: Only one lifecycle per product key can use the `+` (greater than or equal to) syntax.
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2+, beta 9.0+`
+- **Valid range order**: In ranges, the first version must be less than or equal to the second version.
+ - ✅ `stack: preview 9.0-9.2`
+ - ❌ `stack: preview 9.2-9.0`
+- **No version overlaps**: Versions for the same key cannot overlap (ranges are inclusive).
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2+, beta 9.0-9.2`
+
### Page level
Page level annotations are added in the YAML frontmatter, starting with the `applies_to` key and following the [key-value reference](#key-value-reference). For example:
@@ -134,6 +169,22 @@ Use the following key-value reference to find the appropriate key and value for
## Examples
+### Version Syntax Examples
+
+The following table demonstrates the various version syntax options and their rendered output:
+
+| Source Syntax | Description | Badge Display | Notes |
+|:-------------|:------------|:--------------|:------|
+| `stack: ga 9.1` | Greater than or equal to 9.1 | `Stack│9.1+` | Default behavior, equivalent to `9.1+` |
+| `stack: ga 9.1+` | Explicit greater than or equal to | `Stack│9.1+` | Explicit `+` syntax |
+| `stack: preview 9.0-9.2` | Range from 9.0 to 9.2 (inclusive) | `Stack│Preview 9.0-9.2` | Shows range if 9.2.0 is released |
+| `stack: preview 9.0-9.3` | Range where end is unreleased | `Stack│Preview 9.0+` | Shows `+` if 9.3.0 is not released |
+| `stack: beta =9.1` | Exact version 9.1 only | `Stack│Beta 9.1` | No `+` symbol for exact versions |
+| `stack: ga 9.2+, beta 9.0-9.1` | Multiple lifecycles | `Stack│9.2+` | Only highest priority lifecycle shown |
+| `stack: ga 9.3, beta 9.1+` | Unreleased GA with Preview | `Stack│Beta 9.1+` | Shows Beta when GA unreleased with 2+ lifecycles |
+| `serverless: ga` | No version (base 99999) | `Serverless` | No version badge for unversioned products |
+| `deployment:` ` ece: ga 9.0+` | Nested deployment syntax | `ECE│9.0+` | Deployment products shown separately |
+
### Versioning examples
Versioned products require a `version` tag to be used with the `lifecycle` tag:
@@ -240,22 +291,46 @@ applies_to:
## Look and feel
+### Version Syntax Demonstrations
+
+:::::{dropdown} New version syntax examples
+
+The following examples demonstrate the new version syntax capabilities:
+
+**Greater than or equal to:**
+- {applies_to}`stack: ga 9.1` (implicit `+`)
+- {applies_to}`stack: ga 9.1+` (explicit `+`)
+- {applies_to}`stack: preview 9.0+`
+
+**Ranges:**
+- {applies_to}`stack: preview 9.0-9.2` (range display when both released)
+- {applies_to}`stack: beta 9.1-9.3` (converts to `+` if end unreleased)
+
+**Exact versions:**
+- {applies_to}`stack: beta =9.1` (no `+` symbol)
+- {applies_to}`stack: deprecated =9.0`
+
+**Multiple lifecycles:**
+- {applies_to}`stack: ga 9.2+, beta 9.0-9.1` (shows highest priority)
+
+:::::
+
### Block
:::::{dropdown} Block examples
```{applies_to}
-stack: preview 9.1
+stack: preview 9.1+
serverless: ga
-apm_agent_dotnet: ga 1.0.0
-apm_agent_java: beta 1.0.0
-edot_dotnet: preview 1.0.0
+apm_agent_dotnet: ga 1.0+
+apm_agent_java: beta 1.0+
+edot_dotnet: preview 1.0+
edot_python:
-edot_node: ga 1.0.0
-elasticsearch: preview 9.0.0
-security: removed 9.0.0
-observability: deprecated 9.0.0
+edot_node: ga 1.0+
+elasticsearch: preview 9.0+
+security: removed 9.0
+observability: deprecated 9.0+
```
:::::
@@ -331,4 +406,207 @@ Within the ProductApplicability category, EDOT and APM Agent items are sorted al
:::{note}
Inline applies annotations are rendered in the order they appear in the source file.
-:::
\ No newline at end of file
+:::
+
+## Rulesets
+
+Badges and applicabilities are displayed according to pre-defined rules according to the release status, the amount of lifecycles declared in the `applies_to` statement, and the versions involved in the comparison when applicable.
+
+### Badges
+
+:::::{dropdown} No version declared (Serverless)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | – | – | `{product}` |
+| Preview | – | – | `{product}\|Preview` |
+| Beta | – | – | `{product} |Beta` |
+| Deprecated | – | – | `{product} |Deprecated` |
+| Removed | – | – | `{product} |Removed` |
+| Unavailable | – | – | `{product} |Unavailable` |
+
+:::::
+
+:::::{dropdown} No version declared (Other versioning systems)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | – | – | `{product} |{base}+` |
+| Preview | – | – | `{product}\|Preview {base}+` |
+| Beta | – | – | `{product} |Beta {base}+` |
+| Deprecated | – | – | `{product} |Deprecated {base}+` |
+| Removed | – | – | `{product} |Removed {base}+` |
+| Unavailable | – | – | `{product} |Unavailable {base}+` |
+
+:::::
+
+:::::{dropdown} Greater than or equal to "x.x" (x.x+, x.x, x.x.x+, x.x.x)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | Released | \>= 1 | `{product} |x.x+` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Preview | Released | \>= 1 | `{product}\|Preview x.x+` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Beta | Released | \>= 1 | `{product} |Beta x.x+` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Deprecated | Released | \>= 1 | `{product} |Deprecated x.x+` |
+| | Unreleased | 1 | `{product} |Deprecation planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Removed | Released | \>= 1 | `{product} |Removed x.x` |
+| | Unreleased | 1 | `{product} |Removal planned` |
+| | | \>= 2 | Use previous lifecycle |
+
+:::::
+
+:::::{dropdown} Range of "x.x-y.y" (x.x-y.y, x.x.x-y.y.y)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | `y.y.y` is released | \>= 1 | `{product} |x.x-y.y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product} |x.x+` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Preview | `y.y.y` is released | \>= 1 | `{product}\|Preview x.x-y.y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product}\|Preview x.x+` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Beta | `y.y.y` is released | \>= 1 | `{product} |Beta x.x-y.y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product} |Beta x.x+` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Deprecated | `y.y.y` is released | \>= 1 | `{product} |Deprecated x.x-y.y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product} |Deprecated x.x+` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | `{product} |Deprecation planned` |
+| Removed | `y.y.y` is released | \>= 1 | `{product} |Removed x.x` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product} |Removed x.x` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | `{product} |Removal planned` |
+| Unavailable | `y.y.y` is released | \>= 1 | `{product} |Unavailable X.X-Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `{product} |Unavailable X.X+` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | ??? |
+
+:::::
+
+:::::{dropdown} Exactly "x.x" (=x.x, =x.x.x)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | Released | \>= 1 | `{product} |X.X` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Preview | Released | \>= 1 | `{product}\|Preview X.X` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Beta | Released | \>= 1 | `{product} |Beta X.X` |
+| | Unreleased | 1 | `{product}\|Planned` |
+| | | \>= 2 | Use previous lifecycle |
+| Deprecated | Released | \>= 1 | `{product} |Deprecated X.X` |
+| | Unreleased | \>= 1 | `{product} |Deprecation planned` |
+| Removed | Released | \>= 1 | `{product} |Removed X.X` |
+| | Unreleased | \>=1 | `{product} |Removal planned` |
+| Unavailable | Released | \>= 1 | `{product} |Unavailable X.X` |
+| | Unreleased | \>= 1 | ??? |
+
+:::::
+
+### Headers for dynamic content on popover (Applicability list)
+
+:::::{dropdown} No version declared (Serverless)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | – | 1 | `Generally available` |
+| Preview | – | 1 | `Preview` |
+| Beta | – | 1 | `Beta` |
+| Deprecated | – | 1 | `Deprecated` |
+| Removed | – | 1 | `Removed` |
+| Unavailable | – | 1 | `Unavailable` |
+:::::
+
+:::::{dropdown} No version declared (Other versioning systems)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | – | 1 | `Generally available since {base}` |
+| Preview | – | 1 | `Preview since {base}` |
+| Beta | – | 1 | `Beta since {base}` |
+| Deprecated | – | 1 | `Deprecated since {base}` |
+| Removed | – | 1 | `Removed in {base}` |
+| Unavailable | – | 1 | `Unavailable since {base}` |
+:::::
+
+:::::{dropdown} Greater than or equal to "x.x" (x.x+, x.x, x.x.x+, x.x.x)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | Released | \>= 1 | `Generally available since X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Preview | Released | \>= 1 | `Preview since X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Beta | Released | \>= 1 | `Beta since X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Deprecated | Released | \>= 1 | `Deprecated since X.X` |
+| | Unreleased | \>= 1 | `Planned for deprecation` |
+| Removed | Released | \>= 1 | `Removed in X.X` |
+| | Unreleased | \>=1 | `Planned for removal` |
+| Unavailable | Released | \>= 1 | `Unavailable since X.X` |
+| | Unreleased | 1 | `Unavailable` |
+| | | \>= 2 | Do not add to availability list |
+:::::
+
+:::::{dropdown} Range of "x.x-y.y" (x.x-y.y, x.x.x-y.y.y)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | `y.y.y` is released | \>= 1 | `Generally available from X.X to Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Generally available since X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Preview | `y.y.y` is released | \>= 1 | `Preview from X.X to Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Preview since X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Beta | `y.y.y` is released | \>= 1 | `Beta from X.X to Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Beta since X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Deprecated | `y.y.y` is released | \>= 1 | `Deprecated from X.X to Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Deprecated since X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | `Planned for deprecation` |
+| Removed | `y.y.y` is released | \>= 1 | `Removed in X.X` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Removed in X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | `Planned for removal` |
+| Unavailable | `y.y.y` is released | \>= 1 | `Unavailable from X.X to Y.Y` |
+| | `y.y.y` is **not** released `x.x.x` is released | \>= 1 | `Unavailable since X.X` |
+| | `y.y.y` is **not** released `x.x.x` is **not** released | \>= 1 | Do not add to availability list |
+
+
+:::::
+
+:::::{dropdown} Exactly "x.x" (=x.x, =x.x.x)
+
+| Lifecycle | Release status | Lifecycle count | Rendered output |
+|:------------|:--------------------------------------------------------|-----------------|:-----------------------------|
+| GA | Released | \>= 1 | `Generally available in X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Preview | Released | \>= 1 | `Preview in X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Beta | Released | \>= 1 | `Beta in X.X` |
+| | Unreleased | 1 | `Planned` |
+| | | \>= 2 | Do not add to availability list |
+| Deprecated | Released | \>= 1 | `Deprecated in X.X` |
+| | Unreleased | \>= 1 | `Planned for deprecation` |
+| Removed | Released | \>= 1 | `Removed in X.X` |
+| | Unreleased | \>=1 | `Planned for removal` |
+| Unavailable | Released | \>= 1 | `Unavailable in X.X` |
+| | Unreleased | \>= 1 | Do not add to availability list |
+
+:::::
diff --git a/docs/testing/req.md b/docs/testing/req.md
index 95907215f..a9cd202d0 100644
--- a/docs/testing/req.md
+++ b/docs/testing/req.md
@@ -8,24 +8,109 @@ mapped_pages:
---
# Requirements
+This page demonstrates various `applies_to` version syntax examples.
+
+## Version specifier examples
+
+### Greater than or equal (default)
+
+```{applies_to}
+stack: ga 9.0
+```
+
+This is equivalent to `ga 9.0+` — the feature is available from version 9.0 onwards.
+
+### Explicit range
+
+```{applies_to}
+stack: beta 9.0-9.1, ga 9.2
+```
+
+The feature was in beta from 9.0 to 9.1 (inclusive), then became GA in 9.2+.
+
+### Exact version
+
+```{applies_to}
+stack: preview =9.0, ga 9.1
+```
+
+The feature was in preview only in version 9.0 (exactly), then became GA in 9.1+.
+
+## Implicit version inference examples
+
+### Simple two-stage lifecycle
+
```{applies_to}
stack: preview 9.0, ga 9.1
```
-1. Select **Create** to create a new policy, or select **Edit** {icon}`pencil` to open an existing policy.
-1. Select **Create** to create a new policy, or select **Edit** {icon}`logo_vulnerability_management` to open an existing policy.
+Interpreted as: `preview =9.0` (exact), `ga 9.1+` (open-ended).
+### Multi-stage lifecycle with consecutive versions
-{applies_to}`stack: preview 9.0` This tutorial is based on Elasticsearch 9.0.
-This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0.
-This tutorial is based on Elasticsearch 9.0.
+```{applies_to}
+stack: preview 9.0, beta 9.1, ga 9.2
+```
-what
+Interpreted as: `preview =9.0`, `beta =9.1`, `ga 9.2+`.
+### Multi-stage lifecycle with gaps
-To follow this tutorial you will need to install the following components:
+```{applies_to}
+stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4
+```
+
+Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3` (range to fill the gap), `ga 9.4+`.
+
+### Three stages with varying gaps
+
+```{applies_to}
+stack: preview 8.0, beta 9.1, ga 9.3
+```
+
+Interpreted as: `preview 8.0-8.19`, `beta 9.0-9.1`, `ga 9.2+`.
+
+## Inline examples
+{applies_to}`stack: preview 9.0` This feature is in preview in 9.0.
+{applies_to}`stack: beta 9.0-9.1` This feature was in beta from 9.0 to 9.1.
+
+{applies_to}`stack: ga 9.2+` This feature is generally available since 9.2.
+
+{applies_to}`stack: preview =9.0` This feature was in preview only in 9.0 (exact).
+
+## Deprecation and removal examples
+
+```{applies_to}
+stack: deprecated 9.2, removed 9.5
+```
+
+Interpreted as: `deprecated 9.2-9.4`, `removed 9.5+`.
+
+{applies_to}`stack: deprecated 9.0` This feature is deprecated starting in 9.0.
+
+{applies_to}`stack: removed 9.2` This feature was removed in 9.2.
+
+## Mixed deployment examples
+
+```{applies_to}
+stack: ga 9.0
+deployment:
+ ece: ga 4.0
+ eck: beta 3.0, ga 3.1
+```
+
+### Handling multiple future versions
+
+```{applies_to}
+eck: beta 3.4, ga 3.5, deprecated 3.9
+```
+
+
+## Additional content
+
+To follow this tutorial you will need to install the following components:
- An installation of Elasticsearch, based on our hosted [Elastic Cloud](https://www.elastic.co/cloud) service (which includes a free trial period), or a self-hosted service that you run on your own computer. See the Install Elasticsearch section above for installation instructions.
- A [Python](https://python.org) interpreter. Make sure it is a recent version, such as Python 3.8 or newer.
@@ -36,5 +121,20 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen
- The [Flask](https://flask.palletsprojects.com/) web framework for Python.
- The command prompt or terminal application in your operating system.
-
{applies_to}`ece: removed`
+
+{applies_to}`ece: `
+
+{applies_to}`stack: deprecated 7.16.0, removed 8.0.0`
+
+{applies_to}`ess: `
+
+{applies_to}`stack: preview 9.0, ga 9.2, deprecated 9.7`
+
+{applies_to}`stack: preview 9.0, removed 9.1`
+
+{applies_to}`stack: preview 9.0.0-9.0.3, removed 9.3`
+
+{applies_to}`stack: preview 9.0, ga 9.4, removed 9.7`
+
+{applies_to}`stack: preview 9.0, deprecated 9.4, removed 9.7`
diff --git a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
index b2c82a1ec..d94f5ca7c 100644
--- a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
+++ b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
@@ -209,7 +209,7 @@ private bool ShouldIncludeOperation(OpenApiOperation operation, string product)
return true; // Could not parse version, safe to include
// Get current version for the product
- var versioningSystemId = product == "elasticsearch"
+ var versioningSystemId = product.Equals("elasticsearch", StringComparison.OrdinalIgnoreCase)
? VersioningSystemId.Stack
: VersioningSystemId.Stack; // Both use Stack for now
@@ -294,14 +294,14 @@ private static ProductLifecycle ParseLifecycle(string stateValue)
///
/// Parses the version from "Added in X.Y.Z" pattern in the x-state string.
///
- private static SemVersion? ParseVersion(string stateValue)
+ private static VersionSpec? ParseVersion(string stateValue)
{
var match = AddedInVersionRegex().Match(stateValue);
if (!match.Success)
return null;
var versionString = match.Groups[1].Value;
- return SemVersion.TryParse(versionString, out var version) ? version : null;
+ return VersionSpec.TryParse(versionString, out var version) ? version : null;
}
///
diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs b/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs
index 3a4ab61f0..de122351f 100644
--- a/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs
+++ b/src/Elastic.Documentation.Configuration/Versions/VersionConfiguration.cs
@@ -151,4 +151,6 @@ public record VersioningSystem
[YamlMember(Alias = "current")]
public required SemVersion Current { get; init; }
+
+ public bool IsVersioned() => Base.Major != AllVersions.Instance.Major;
}
diff --git a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs
index 102dca32f..08259d8ae 100644
--- a/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs
+++ b/src/Elastic.Documentation.Configuration/Versions/VersionInference.cs
@@ -96,7 +96,7 @@ public class NoopVersionInferrer : IVersionInferrerService
public VersioningSystem InferVersion(string repositoryName, IReadOnlyCollection? legacyPages, IReadOnlyCollection? products, ApplicableTo? applicableTo) => new()
{
Id = VersioningSystemId.Stack,
- Base = new SemVersion(0, 0, 0),
- Current = new SemVersion(0, 0, 0)
+ Base = ZeroVersion.Instance,
+ Current = ZeroVersion.Instance
};
}
diff --git a/src/Elastic.Documentation.Site/Assets/main.ts b/src/Elastic.Documentation.Site/Assets/main.ts
index 38f9fcfd6..0beab1d5e 100644
--- a/src/Elastic.Documentation.Site/Assets/main.ts
+++ b/src/Elastic.Documentation.Site/Assets/main.ts
@@ -2,7 +2,6 @@ import { initAppliesSwitch } from './applies-switch'
import { initCopyButton } from './copybutton'
import { initHighlight } from './hljs'
import { initImageCarousel } from './image-carousel'
-import './markdown/applies-to'
import { openDetailsWithAnchor } from './open-details-with-anchor'
import { initNav } from './pages-nav'
import { initSmoothScroll } from './smooth-scroll'
@@ -33,6 +32,7 @@ initializeOtel({
// Parcel will automatically code-split this into a separate chunk
import('./web-components/SearchOrAskAi/SearchOrAskAi')
import('./web-components/VersionDropdown')
+import('./web-components/AppliesToPopover')
const { getOS } = new UAParser()
const isLazyLoadNavigationEnabled =
diff --git a/src/Elastic.Documentation.Site/Assets/markdown/applies-to.css b/src/Elastic.Documentation.Site/Assets/markdown/applies-to.css
index d371819df..1e74fec30 100644
--- a/src/Elastic.Documentation.Site/Assets/markdown/applies-to.css
+++ b/src/Elastic.Documentation.Site/Assets/markdown/applies-to.css
@@ -4,14 +4,58 @@
@apply text-subdued;
- [data-tippy-content]:not([data-tippy-content='']) {
- @apply cursor-help;
+ applies-to-popover {
+ display: contents;
}
.applicable-info {
@apply border-grey-20 inline-flex cursor-default rounded-full border-[1px] bg-white pt-1.5 pr-3 pb-1.5 pl-3;
}
+ .applicable-info--clickable {
+ /* Desktop: tooltip-like behavior, no pointer cursor */
+ @apply cursor-default;
+
+ &:hover {
+ @apply border-grey-30 bg-grey-10;
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ /* Desktop: no focus-visible outline for tooltip behavior */
+ @media (hover: hover) and (pointer: fine) {
+ &:focus-visible {
+ outline: none;
+ }
+ }
+
+ /* Mobile/touch: show focus outline for accessibility */
+ @media (hover: none), (pointer: coarse) {
+ @apply cursor-pointer;
+
+ &:focus-visible {
+ outline: 2px solid var(--color-blue-elastic);
+ outline-offset: 2px;
+ }
+ }
+ }
+
+ /* Desktop: no pinned state styling */
+ @media (hover: hover) and (pointer: fine) {
+ .applicable-info--pinned {
+ @apply border-grey-20 bg-white;
+ }
+ }
+
+ /* Mobile/touch: show pinned state */
+ @media (hover: none), (pointer: coarse) {
+ .applicable-info--pinned {
+ @apply border-blue-elastic bg-grey-10;
+ }
+ }
+
.applicable-meta {
@apply inline-flex gap-1.5;
}
@@ -35,6 +79,12 @@
.applies.applies-inline {
display: inline-block;
vertical-align: bottom;
+
+ applies-to-popover {
+ display: inline-flex;
+ vertical-align: bottom;
+ }
+
.applicable-separator {
margin-left: calc(var(--spacing) * 1.5);
margin-right: calc(var(--spacing) * 1.5);
@@ -57,19 +107,9 @@
}
}
-.tippy-box[data-theme~='applies-to'] {
- .tippy-content {
- white-space: normal;
-
- strong {
- display: block;
- margin-bottom: calc(var(--spacing) * 1);
- }
- }
-
- .tippy-content > div:not(:last-child) {
- border-bottom: 1px dotted var(--color-grey-50);
- padding-bottom: calc(var(--spacing) * 3);
- margin-bottom: calc(var(--spacing) * 3);
- }
+.euiPopover__panel {
+ /* Shadow and border for the popover */
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1) !important;
+ border: 1px solid var(--color-grey-20) !important;
+ border-radius: 6px !important;
}
diff --git a/src/Elastic.Documentation.Site/Assets/markdown/applies-to.ts b/src/Elastic.Documentation.Site/Assets/markdown/applies-to.ts
deleted file mode 100644
index df5ad9d66..000000000
--- a/src/Elastic.Documentation.Site/Assets/markdown/applies-to.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { $$ } from 'select-dom'
-import tippy from 'tippy.js'
-
-document.addEventListener('htmx:load', function () {
- const selector = [
- '.applies [data-tippy-content]:not([data-tippy-content=""])',
- '.applies-inline [data-tippy-content]:not([data-tippy-content=""])',
- ].join(', ')
-
- const appliesToBadgesWithTooltip = $$(selector)
- appliesToBadgesWithTooltip.forEach((badge) => {
- const content = badge.getAttribute('data-tippy-content')
- if (!content) return
- tippy(badge, {
- content,
- allowHTML: true,
- delay: [400, 100],
- hideOnClick: false,
- ignoreAttributes: true,
- theme: 'applies-to',
- })
- })
-})
diff --git a/src/Elastic.Documentation.Site/Assets/web-components/AppliesToPopover.tsx b/src/Elastic.Documentation.Site/Assets/web-components/AppliesToPopover.tsx
new file mode 100644
index 000000000..c5918c61a
--- /dev/null
+++ b/src/Elastic.Documentation.Site/Assets/web-components/AppliesToPopover.tsx
@@ -0,0 +1,493 @@
+'use strict'
+
+import '../eui-icons-cache'
+import { EuiPopover, useGeneratedHtmlId } from '@elastic/eui'
+import { css } from '@emotion/react'
+import r2wc from '@r2wc/react-to-web-component'
+import * as React from 'react'
+import { useState, useRef, useEffect, useCallback } from 'react'
+
+type PopoverAvailabilityItem = {
+ text: string
+ lifecycleDescription?: string
+}
+
+type PopoverData = {
+ productDescription?: string
+ availabilityItems: PopoverAvailabilityItem[]
+ additionalInfo?: string
+ showVersionNote: boolean
+ versionNote?: string
+}
+
+type AppliesToPopoverProps = {
+ badgeKey?: string
+ badgeLifecycleText?: string
+ badgeVersion?: string
+ lifecycleClass?: string
+ lifecycleName?: string
+ showLifecycleName?: boolean
+ showVersion?: boolean
+ hasMultipleLifecycles?: boolean
+ popoverData?: PopoverData
+ showPopover?: boolean
+ isInline?: boolean
+}
+
+const AppliesToPopover = ({
+ badgeKey,
+ badgeLifecycleText,
+ badgeVersion,
+ lifecycleClass,
+ lifecycleName,
+ showLifecycleName,
+ showVersion,
+ hasMultipleLifecycles,
+ popoverData,
+ showPopover = true,
+ isInline = false,
+}: AppliesToPopoverProps) => {
+ const [isOpen, setIsOpen] = useState(false)
+ const [isPinned, setIsPinned] = useState(false)
+ const [openItems, setOpenItems] = useState>(new Set())
+ const [isTouchDevice, setIsTouchDevice] = useState(false)
+ const popoverId = useGeneratedHtmlId({ prefix: 'appliesToPopover' })
+ const contentRef = useRef(null)
+ const badgeRef = useRef(null)
+ const hoverTimeoutRef = useRef | null>(null)
+
+ // Detect touch device on mount
+ useEffect(() => {
+ const checkTouchDevice = () => {
+ const hasCoarsePointer =
+ window.matchMedia('(pointer: coarse)').matches
+ const hasNoHover = window.matchMedia('(hover: none)').matches
+ setIsTouchDevice(hasCoarsePointer || hasNoHover)
+ }
+ checkTouchDevice()
+ // Re-check on resize in case device mode changes (e.g., dev tools toggle)
+ window.addEventListener('resize', checkTouchDevice)
+ return () => window.removeEventListener('resize', checkTouchDevice)
+ }, [])
+
+ const hasPopoverContent =
+ popoverData &&
+ (popoverData.productDescription ||
+ popoverData.availabilityItems.length > 0 ||
+ popoverData.additionalInfo ||
+ popoverData.showVersionNote)
+
+ const openPopover = useCallback(() => {
+ if (showPopover && hasPopoverContent) {
+ setIsOpen(true)
+ }
+ }, [showPopover, hasPopoverContent])
+
+ const closePopover = useCallback(() => {
+ if (!isPinned) {
+ setIsOpen(false)
+ }
+ }, [isPinned])
+
+ const handleClick = useCallback(() => {
+ // Only allow click/pin behavior on touch devices
+ // On desktop, the popover is tooltip-like (hover only)
+ if (!isTouchDevice) return
+
+ if (showPopover && hasPopoverContent) {
+ if (isPinned) {
+ // If already pinned, unpin and close
+ setIsPinned(false)
+ setIsOpen(false)
+ } else {
+ // Pin the popover open
+ setIsPinned(true)
+ setIsOpen(true)
+ }
+ }
+ }, [showPopover, hasPopoverContent, isPinned, isTouchDevice])
+
+ const toggleItem = useCallback((index: number, e: React.MouseEvent) => {
+ e.stopPropagation()
+ setOpenItems((prev) => {
+ const next = new Set(prev)
+ if (next.has(index)) {
+ next.delete(index)
+ } else {
+ next.add(index)
+ }
+ return next
+ })
+ }, [])
+
+ const handleClosePopover = useCallback(() => {
+ setIsPinned(false)
+ setIsOpen(false)
+ }, [])
+
+ const handleMouseEnter = useCallback(() => {
+ // Clear any pending close timeout
+ if (hoverTimeoutRef.current) {
+ clearTimeout(hoverTimeoutRef.current)
+ hoverTimeoutRef.current = null
+ }
+ openPopover()
+ }, [openPopover])
+
+ const handleMouseLeave = useCallback(() => {
+ // Small delay before closing to allow moving to the popover content
+ hoverTimeoutRef.current = setTimeout(() => {
+ closePopover()
+ }, 100)
+ }, [closePopover])
+
+ // Cleanup timeout on unmount
+ useEffect(() => {
+ return () => {
+ if (hoverTimeoutRef.current) {
+ clearTimeout(hoverTimeoutRef.current)
+ }
+ }
+ }, [])
+
+ // Close popover when badge becomes hidden (e.g., parent details element collapses)
+ useEffect(() => {
+ if (!badgeRef.current || !isOpen) return
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ // If badge is no longer visible, close the popover
+ if (!entry.isIntersecting) {
+ setIsPinned(false)
+ setIsOpen(false)
+ }
+ })
+ },
+ { threshold: 0 }
+ )
+
+ observer.observe(badgeRef.current)
+
+ return () => {
+ observer.disconnect()
+ }
+ }, [isOpen])
+
+ // Reset open items when popover closes
+ useEffect(() => {
+ if (!isOpen) {
+ setOpenItems(new Set())
+ }
+ }, [isOpen])
+
+ const showSeparator =
+ badgeKey && (showLifecycleName || showVersion || badgeLifecycleText)
+
+ // Only show interactive attributes on touch devices
+ const isInteractive = showPopover && hasPopoverContent && isTouchDevice
+
+ const badgeButton = (
+ {
+ if (isInteractive && (e.key === 'Enter' || e.key === ' ')) {
+ e.preventDefault()
+ handleClick()
+ }
+ }}
+ >
+ {badgeKey}
+
+ {showSeparator && }
+
+
+ {showLifecycleName && (
+
+ {lifecycleName}
+
+ )}
+ {showVersion ? (
+
+ {badgeVersion}
+
+ ) : (
+ badgeLifecycleText
+ )}
+ {hasMultipleLifecycles && (
+
+
+
+
+
+ )}
+
+
+ )
+
+ if (!showPopover || !hasPopoverContent) {
+ return badgeButton
+ }
+
+ const renderAvailabilityItem = (
+ item: PopoverAvailabilityItem,
+ index: number
+ ) => {
+ const isItemOpen = openItems.has(index)
+
+ if (item.lifecycleDescription) {
+ return (
+
"""
// Test complex scenarios with multiple lifecycles
-type ``mixed lifecycles with ga planned`` () =
+type ``mixed unreleased lifecycles falls back to planned`` () =
static let markdown = Setup.Markdown """
```{applies_to}
stack: ga 8.8.0, preview 8.1.0
@@ -401,27 +291,11 @@ stack: ga 8.8.0, preview 8.1.0
"""
[]
- let ``renders GA planned when preview exists alongside GA`` () =
+ let ``renders Planned when GA and Preview are both unreleased`` () =
markdown |> convertsToHtml """
+"""
-Beta features are subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.">
- Stack
-
-
- Planned
-
-
-
- ECE
-
-
- Planned
-
-
+type ``stack empty defaults to ga`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack:
+```
+"""
+
+ []
+ let ``no version defaults to ga`` () =
+ markdown |> convertsToHtml """
+
+
+
"""
-// Test missing version scenarios
-type ``version scenarios missing`` () =
+// Test missing VersioningSystemId coverage
+type ``all products future version coverage`` () =
static let markdown = Setup.Markdown """
```{applies_to}
-stack: beta 9.1.0
+stack: ga 9.0.0
+serverless: ga 9.0.0
deployment:
- ece: ga 9.1.0
+ ece: ga 9.0.0
+ eck: ga 9.0.0
+ ess: ga 9.0.0
+ self: ga 9.0.0
+product: ga 9.0.0
```
"""
[]
- let ``renders missing version scenarios`` () =
+ let ``renders VersioningSystemId coverage`` () =
markdown |> convertsToHtml """
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
-Beta features are subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.">
- Stack
-
-
- Planned
-
-
-
- ECE
-
-
- Planned
-
-
+// Test multiple lifecycles for same applicability key
+// With version inference: ga 8.0, beta 8.1 → ga =8.0 (exact), beta 8.1+ (highest gets GTE)
+type ``ga with beta uses version inference`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: ga 8.0.0, beta 8.1.0
+```
+"""
+
+ []
+ let ``renders multiple lifecycles with ellipsis and shows GA lifecycle`` () =
+ markdown |> convertsToHtml """
+
+
+
"""
-// Test missing edge cases
-type ``edge cases missing`` () =
+type ``stack ga released version`` () =
static let markdown = Setup.Markdown """
```{applies_to}
-stack:
+stack: ga 7.0.0
+```
+"""
+
+ []
+ let ``renders ga since released version`` () =
+ markdown |> convertsToHtml """
+
+
+
+
+"""
+
+type ``stack preview released version`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: preview 7.0.0
```
"""
[]
- let ``renders missing edge cases`` () =
+ let ``renders preview since released version`` () =
markdown |> convertsToHtml """
-
+
+
+"""
+
+type ``stack beta released version`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: beta 7.0.0
+```
+"""
-If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page.">
- Stack
-
-
-
+ []
+ let ``renders beta since released version`` () =
+ markdown |> convertsToHtml """
+
+
+
"""
-// Test missing VersioningSystemId coverage
-type ``versioning system id coverage`` () =
+type ``stack deprecated released version`` () =
static let markdown = Setup.Markdown """
```{applies_to}
-stack: ga 9.0.0
-serverless: ga 9.0.0
-deployment:
- ece: ga 9.0.0
- eck: ga 9.0.0
- ess: ga 9.0.0
- self: ga 9.0.0
-product: ga 9.0.0
+stack: deprecated 7.0.0
```
"""
[]
- let ``renders missing VersioningSystemId coverage`` () =
- markdown |> convertsToHtml """
-
-"""
-
-// Test missing disclaimer scenarios
-type ``disclaimer scenarios`` () =
+ let ``renders deprecated since released version`` () =
+ markdown |> convertsToHtml """
+
+
+
+
+"""
+
+type ``stack removed released version`` () =
static let markdown = Setup.Markdown """
```{applies_to}
-stack: ga 9.0.0
+stack: removed 7.0.0
```
"""
[]
- let ``renders missing disclaimer scenarios`` () =
+ let ``renders removed in released version`` () =
markdown |> convertsToHtml """
-
+
+
+"""
+
+// Version spec syntax tests (exact and range)
+type ``stack ga exact version released`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: ga =7.5
+```
+"""
-If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page.">
- Stack
-
-
- Planned
-
-
+ []
+ let ``renders ga in exact released version`` () =
+ markdown |> convertsToHtml """
+
+
+
"""
-// Test multiple lifecycles for same applicability key
-type ``multiple lifecycles same key`` () =
+type ``stack ga range both released`` () =
static let markdown = Setup.Markdown """
```{applies_to}
-stack: ga 8.0.0, beta 8.1.0
+stack: ga 7.0-8.0
```
"""
[]
- let ``renders multiple lifecycles with ellipsis and shows GA lifecycle`` () =
+ let ``renders ga from-to when both ends released`` () =
markdown |> convertsToHtml """
-
+
+
+"""
-If this functionality is unavailable or behaves differently when deployed on ECH, ECE, ECK, or a self-managed installation, it will be indicated on the page.
+type ``stack ga range max unreleased`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: ga 7.0-9.0
+```
+"""
-
Elastic Stack Beta 8.1.0:We plan to add this functionality in a future Elastic Stack update. Subject to change.
+ []
+ let ``renders ga since min when max unreleased`` () =
+ markdown |> convertsToHtml """
+
+
+
+
+"""
-Beta features are subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.
">
- Stack
-
-
- GA
-
- 8.0.0
-
-
-
-
-
-
-
-
+// Multiple released lifecycles showing both in popover
+type ``preview and ga both released`` () =
+ static let markdown = Setup.Markdown """
+```{applies_to}
+stack: preview 7.0, ga 7.5
+```
+"""
+
+ []
+ let ``renders ga badge with both lifecycles in popover`` () =
+ markdown |> convertsToHtml """
+
+
+
"""
diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs
index 2d1f02b95..95483372e 100644
--- a/tests/authoring/Applicability/AppliesToFrontMatter.fs
+++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs
@@ -163,10 +163,7 @@ applies_to:
[]
let ``apply matches expected`` () =
markdown |> appliesTo (ApplicableTo(
- Product=AppliesCollection([
- Applicability.op_Explicit "removed 9.7";
- Applicability.op_Explicit "preview 9.5"
- ] |> Array.ofList)
+ Product=AppliesCollection.op_Explicit "removed 9.7, preview 9.5"
))
type ``lenient to defining types at top level`` () =
diff --git a/tests/authoring/Blocks/Admonitions.fs b/tests/authoring/Blocks/Admonitions.fs
index d7efdb64b..63525a2c2 100644
--- a/tests/authoring/Blocks/Admonitions.fs
+++ b/tests/authoring/Blocks/Admonitions.fs
@@ -64,13 +64,7 @@ This is a custom admonition with applies_to information.
Note
-
- Stack
-
-
-
+
@@ -82,11 +76,7 @@ If this functionality is unavailable or behaves differently when deployed on ECH
Warning
-
- Serverless
-
-
-
+
@@ -98,15 +88,7 @@ If this functionality is unavailable or behaves differently when deployed on ECH