feat: version aware minimum version badge - #2327
Conversation
- Add minimalVersion to docs collection schemas (docsv3, docsv4, docsv5) - Render version badge in docs page header when frontmatter has minimalVersion - Badge shows vX.Y (e.g. v3.12), info color, with aria-label for a11y Complements nuxt/nuxt#34485: API docs set minimalVersion in frontmatter; this repo displays the badge so global inline badges are not needed.
…e to trimmed string
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: Robin <robin.kehl@singular-it.de>
|
@OrbisK is attempting to deploy a commit to the Nuxt Team on Vercel. A member of the Team first needs to authorize it. |
Removes the z.preprocess wrapper around minimalVersion in the docsv3/ docsv4/docsv5 collection schemas in favor of a plain z.string().optional(). Trimming is now done consistently in the docs page template instead. This is a defensive simplification; it does not touch the underlying cause of the Vercel preview build failure (SQLITE_UNKNOWN: table _content_docsv3 has 13 columns but 14 values were supplied), which looks like a stale/pre-existing _content_docsv3 table on the preview's content database predating this schema change. Nuxt Content only ever emits CREATE TABLE IF NOT EXISTS (never ALTER TABLE), so an existing table with the old 13-column shape won't pick up the new column automatically; a redeploy without build cache is likely needed to fully resolve the build failure.
Keeps the VersionBadge component (tolerance-aware) over the inline UBadge from the PR, and takes the PR's simplified `minimalVersion: z.string()` content schema. Also brings in upstream main, which the PR head had merged.
|
@HugoRCD I think its ready for review. I have extracted a composable that performs all the tolerance logic. Versions in the sidebar are only displayed if the minor version is within the last two minor versions, so as to reduce the noise. We can adjust this value here: Other badges (from the documentation tables and headlines) are displayed if they match the current major version. Higher versions are always displayed, just in case we merge some v4 documents into v3 documents. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds version keyword formatting, release and nightly paths, semver tolerance checks, latest-version resolution with cancellable requests, and release article lookup. Navigation data now includes minimum-version metadata. Documentation pages recursively apply version badges to navigation items and render linked Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@content.config.ts`:
- Around line 220-239: Update the shared collection schema used by docsv3,
docsv4, and docsv5 so minimalVersion accepts both YAML strings and numbers,
trims the normalized string value, and remains optional. Reuse this schema
across all three minimalVersion fields instead of keeping z.string().optional()
separately.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 92c220a9-f9ec-4ba8-8289-b8f2db63574d
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (8)
app/components/content/VersionBadge.vueapp/composables/useDocsVersion.tsapp/composables/useVersionBadge.tsapp/pages/docs/[...slug].vueapp/utils/version.tscontent.config.tspackage.jsonserver/api/navigation.json.get.ts
| links: z.array(Button), | ||
| minimalVersion: z.string().optional() | ||
| }) | ||
| }), | ||
| docsv4: defineCollection({ | ||
| type: 'page', | ||
| source: [docsV4Source, examplesV4Source], | ||
| schema: z.object({ | ||
| titleTemplate: z.string().optional(), | ||
| links: z.array(Button) | ||
| links: z.array(Button), | ||
| minimalVersion: z.string().optional() | ||
| }) | ||
| }), | ||
| docsv3: defineCollection({ | ||
| type: 'page', | ||
| source: [docsV3Source, examplesV3Source], | ||
| schema: z.object({ | ||
| titleTemplate: z.string().optional(), | ||
| links: z.array(Button) | ||
| links: z.array(Button), | ||
| minimalVersion: z.string().optional() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate content.config.ts =="
fd -a 'content\.config\.ts$' . || true
echo "== relevant lines =="
if [ -f content.config.ts ]; then
nl -ba content.config.ts | sed -n '1,280p'
fi
echo "== search minimalVersion usages =="
rg -n "minimalVersion|TitleTemplate|minimal|version" -S --glob '!node_modules' --glob '!dist' --glob '!build' . | head -200Repository: nuxt/nuxt.com
Length of output: 280
🌐 Web query:
Zod 4 z.string input values number string accepts number transforms number to string optional
💡 Result:
In Zod, standard string schemas (defined via z.string) are strict and do not accept numbers as valid input; they expect input of type string [1][2][3]. If you pass a number to z.string, Zod will throw a validation error [3][4]. To handle cases where you need to accept numbers and convert them into strings, or to make the input optional, you should use the following patterns: Accepting numbers and converting them to strings If you want to accept a number (or other input) and coerce it into a string during parsing, use z.coerce.string [5][6]: const schema = z.coerce.string; schema.parse(123); // "123" schema.parse("hello"); // "hello" Unlike.transform, which runs after validation, z.coerce.string runs before validation and uses the JavaScript String constructor to convert the input [5][6]. Making the string optional To make any schema optional (including coerced ones), chain the.optional method [3][4]. This allows the value to be undefined: const schema = z.coerce.string.optional; schema.parse(undefined); // undefined schema.parse(123); // "123" Summary of approaches - For strict string validation: Use z.string [1][2]. - For flexible input (accepting numbers and converting to strings): Use z.coerce.string [5][6]. - For optional fields: Append.optional to your schema [3][4]. - For custom transformation logic: If coercion via String is insufficient, you can use.transform to convert data after initial validation, or.preprocess to handle inputs before validation [7][4][5].
Citations:
- 1: https://github.com/colinhacks/zod/blob/v4.4.3/packages/docs/content/api.mdx
- 2: https://zod.dev/api
- 3: https://zod.dev/packages/zod
- 4: https://github.com/colinhacks/zod/blob/9575cfecbabeb149a4991f931cf24f685ea8c55e/README.md
- 5: https://mintlify.com/colinhacks/zod/api/utilities/coerce
- 6: https://github.com/colinhacks/zod/blob/c7805073/packages/docs/content/api.mdx
- 7: https://mintlify.wiki/colinhacks/zod/concepts/transformations
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== relevant content.config.ts lines =="
sed -n '1,280p' content.config.ts | awk '{printf "%6d %s\n", NR, $0}'
echo "== search minimalVersion usages =="
rg -n "minimalVersion|minimalVersion:" -S . || true
echo "== package zod versions =="
if [ -f package.json ]; then jq '.dependencies , .devDependencies // {}' package.json | sed -n '1,160p'; fiRepository: nuxt/nuxt.com
Length of output: 13647
🌐 Web query:
Zod 4 z.coerce.string z.string number whitespace trim transforms trim documentation
💡 Result:
In Zod 4, coercion and string transformations function as follows based on the official documentation [1][2]. Zod Coercion The z.coerce namespace is used to automatically convert input data to a specified type before validation [3][4]. For strings, z.coerce.string applies the JavaScript String constructor to the input [1][4]. const schema = z.coerce.string; schema.parse(42); // "42" schema.parse(true); // "true" String Trimming and Transforms Zod provides a built-in.trim method for ZodString schemas, which removes whitespace from both ends of the input string [5][6]. This method returns a new ZodString instance, allowing for further chaining of string-specific validation methods (such as.min,.max,.email, etc.) [7]. Important Considerations: 1. Chaining: Methods like.trim return a ZodString instance, preserving compatibility with other string validations [7]. Conversely, using.transform creates a ZodEffects wrapper, which may obscure string-specific methods unless you use.pipe to restore the schema type [7]. 2. Validation vs. Transformation: Some specific validators (e.g., z.url, z.email) do not automatically trim whitespace, even if the underlying native constructor (like URL) might do so internally [8]. If you require a trimmed input to be validated by such helpers, it is recommended to explicitly use.trim followed by.pipe [8]: z.string.trim.pipe(z.url); 3. Zod 4 Availability: Zod 4 is the current stable major version [2][9]. It introduces significant performance improvements and fixes various long-standing design limitations found in previous versions [2].
Citations:
- 1: https://zod.dev/api
- 2: https://zod.dev/v4
- 3: https://mintlify.com/colinhacks/zod/api/utilities/coerce
- 4: https://github.com/colinhacks/zod/blob/v4.4.3/packages/docs/content/api.mdx
- 5: https://github.com/colinhacks/zod/blob/9575cfecbabeb149a4991f931cf24f685ea8c55e/README.md
- 6: https://github.com/colinhacks/zod/blob/912f0f51/packages/docs/content/api.mdx
- 7: Zod transformations understanding colinhacks/zod#5734
- 8: The new v4 string helpers seem incorrectly validate strings that should fail (or be corrected) colinhacks/zod#4754
- 9: https://zod.dev/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== inspect docs where minimalVersion frontmatter is currently used =="
rg -n "minimalVersion|minimal-version|minimalVersion:" -S . --glob '!node_modules' --glob '!dist' --glob '!build' || true
echo "== inspect consumer code =="
sed -n '1,320p' app/pages/docs/[...slug].vue | awk '{printf "%6d %s\n", NR, $0}'Repository: nuxt/nuxt.com
Length of output: 13958
Accept numeric minimalVersion and normalize it in the collection schema.
docsv3/4/5 schemas use z.string().optional(), so YAML values like minimalVersion: 3.12 validate as numbers and are rejected before badges can normalize them. Use a shared optional string coerced/transformed from both string and number input and trim it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@content.config.ts` around lines 220 - 239, Update the shared collection
schema used by docsv3, docsv4, and docsv5 so minimalVersion accepts both YAML
strings and numbers, trims the normalized string value, and remains optional.
Reuse this schema across all three minimalVersion fields instead of keeping
z.string().optional() separately.
| titleTemplate: z.string().optional(), | ||
| links: z.array(Button) | ||
| links: z.array(Button), | ||
| minimalVersion: z.string().optional() |
There was a problem hiding this comment.
@HugoRCD I think we need the process you removed to ensure that it doesnt fail on numbers 🤔
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
I have added a nightly shortcut to also display pending/unrealeased changes |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/nuxt/index.spec.ts (1)
204-213: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCover threshold boundaries and all tolerance granularities.
versionThresholddrives badge visibility, but the suite only exercises one minor-tolerance path indirectly. Add direct assertions for major, minor-boundary, patch, invalid-latest, and newer-major behavior.Suggested coverage
+ describe('versionThreshold', () => { + it('computes each tolerance granularity', () => { + expect(versionThreshold('4.5.2', { major: 0 })).toBe('4.0.0') + expect(versionThreshold('4.5.2', { minor: 2 })).toBe('4.3.0') + expect(versionThreshold('4.5.2', { patch: 2 })).toBe('4.5.0') + expect(versionThreshold('nope')).toBeUndefined() + }) + }) + expect(satisfiesVersionTolerance('4.4.0', '4.5.2', { minor: 2 })).toBe(true) + expect(satisfiesVersionTolerance('4.3.0', '4.5.2', { minor: 2 })).toBe(true) expect(satisfiesVersionTolerance('4.2.0', '4.5.2', { minor: 2 })).toBe(false) + expect(satisfiesVersionTolerance('5.0.0', '4.5.2', { major: 0 })).toBe(true)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/nuxt/index.spec.ts` around lines 204 - 213, Expand the satisfiesVersionTolerance test suite to directly cover every tolerance granularity and threshold boundary: major tolerance, minor values at and beyond the boundary, patch tolerance, invalid latest-version input, and versions from a newer major release. Keep the existing keyword and minor-range assertions, and assert the expected true/false outcomes for each boundary case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test/nuxt/index.spec.ts`:
- Around line 204-213: Expand the satisfiesVersionTolerance test suite to
directly cover every tolerance granularity and threshold boundary: major
tolerance, minor values at and beyond the boundary, patch tolerance, invalid
latest-version input, and versions from a newer major release. Keep the existing
keyword and minor-range assertions, and assert the expected true/false outcomes
for each boundary case.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 58fe2ffb-25b7-4697-b798-fc915735b2c5
📒 Files selected for processing (5)
app/components/content/VersionBadge.vueapp/composables/useVersionBadge.tsapp/pages/docs/[...slug].vueapp/utils/version.tstest/nuxt/index.spec.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- app/composables/useVersionBadge.ts
- app/pages/docs/[...slug].vue
- app/components/content/VersionBadge.vue
|
converted to draft. I will slighly rework the nightly shortcut.
|
|
Nightly version tags now link to https://nuxt.com/docs/4.x/guide/going-further/nightly-release-channel Others to the minor blog post. (excluding navigation tags) |
|
@OrbisK Am I crazy or is the "soon" not centered? |
what about
🤔 |
Maybe a tiny bit too big, but I'm nitpicking |
| return { | ||
| // The sidebar has no room for `nightly v4` | ||
| 'label': labels.shortLabel, | ||
| 'size': 'sm' as const, | ||
| 'color': 'info' as const, | ||
| 'variant': 'subtle' as const, | ||
| 'aria-label': labels.ariaLabel | ||
| } | ||
| } |
There was a problem hiding this comment.
@HugoRCD if you want to add some classes to the navigation badge, you can do this here
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: Robin <robin.kehl@singular-it.de>
|
had to only uppercase |








🔗 Linked issue
Followup on #2203
📚 Description
This PR aims to only show the minimalVersion Badge, if it matches the current major (of higher - but this should be unlikely), so it reduces noise on e.g. v4 docs by not displaying any v3 related badges that are already included/not relevant.
Possible Followups:
:bage[vX.Y]in the documentation to use:versionBadgeto achive the same effect.