Skip to content

fix(navigation): coerce findPageHeadline return value to string#3786

Open
surohak wants to merge 1 commit intonuxt:mainfrom
surohak:fix/find-page-headline-type-safety
Open

fix(navigation): coerce findPageHeadline return value to string#3786
surohak wants to merge 1 commit intonuxt:mainfrom
surohak:fix/find-page-headline-type-safety

Conversation

@surohak
Copy link
Copy Markdown

@surohak surohak commented May 6, 2026

Description

When a .navigation.yml title contains a colon (e.g. Getting Started: Basics), YAML parsers may interpret it as an object ({ "Getting Started": "Basics" }) instead of a string. findPageHeadline returns link.title directly, which surfaces as [object Object] in consuming components like Docus page headers.

Reproduction

  1. Create a Docus app
  2. In content/1.getting-started/.navigation.yml:
    title: Getting Started: Basics
  3. Run nuxt generate
  4. Any child page in that section shows [object Object] as the headline

Root Cause

link.title is typed as string but YAML parsing can produce an object at runtime. findPageHeadline returns it without validation.

Fix

Coerce link.title to a string before returning, ensuring the function's return type contract is always met:

return typeof link.title === 'string' ? link.title : String(link.title ?? '')

This is a defensive measure — the ideal fix would be upstream in the YAML-to-navigation pipeline, but this ensures consumers never receive non-string values regardless of input.

Changes

  • src/runtime/utils/index.ts: Coerce link.title at both return points in findPageHeadline

Fixes #3754

When a .navigation.yml title contains a colon (e.g. "Getting Started:
Basics"), YAML parsers may interpret it as an object instead of a
string. findPageHeadline returns link.title directly, which surfaces
as [object Object] in consuming components like Docus page headers.

Coerce the title to a string before returning to guarantee the
function's return type contract is met regardless of YAML parsing
quirks.

Fixes nuxt#3754
@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

Someone is attempting to deploy a commit to the Nuxt Team on Vercel.

A member of the Team first needs to authorize it.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 6, 2026

npm i https://pkg.pr.new/@nuxt/content@3786

commit: 7d889f1

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

The change modifies the findPageHeadline function in src/runtime/utils/index.ts to ensure it returns a string value. Two code branches (indexAsChild and non-indexAsChild) that previously returned link.title directly are updated to cast the title to a string with an empty string fallback. This prevents non-string values from being returned and surfacing as [object Object] in headers.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(navigation): coerce findPageHeadline return value to string' clearly and specifically describes the main change—coercing the return value of findPageHeadline to string to fix a navigation bug.
Description check ✅ Passed The description comprehensively explains the YAML parsing issue causing non-string titles, provides reproduction steps, identifies the root cause, and documents the defensive fix applied in the PR.
Linked Issues check ✅ Passed The PR fully addresses issue #3754 by coercing link.title to a string at both return points in findPageHeadline, ensuring the function always returns a string and prevents '[object Object]' from rendering in Docus headers.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objective: only src/runtime/utils/index.ts is modified with the defensive type coercion fix for findPageHeadline, with no extraneous changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/runtime/utils/index.ts (1)

68-68: 💤 Low value

Minor: extract the coercion to avoid duplication.

The expression typeof link.title === 'string' ? link.title : String(link.title ?? '') is duplicated verbatim across two branches. If you keep the current coercion strategy (rather than adopting one of the options above), consider extracting it into a small local helper so future tweaks happen in one place.

+const toTitleString = (t: unknown): string =>
+  typeof t === 'string' ? t : String(t ?? '')

Also applies to: 78-78

🤖 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 `@src/runtime/utils/index.ts` at line 68, Extract the duplicated coercion logic
for link.title into a small local helper (e.g., coerceLinkTitle or
formatLinkTitle) and replace both occurrences of the expression typeof
link.title === 'string' ? link.title : String(link.title ?? '') with a call to
that helper; ensure the helper accepts the link (or title) and returns the same
string result so future changes are centralized and both branches use the single
helper implementation.
🤖 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 `@src/runtime/utils/index.ts`:
- Line 68: The current coercion returns "[object Object]" for YAML-parsed object
titles; replace the String(...) fallback with a best-effort reconstruction
helper: add a helper function reconstructTitleFromObject(value: unknown): string
| undefined that returns the original string for primitives, returns "key:
value" for single-key objects (recursively stringifying nested primitives),
joins array elements with ", " for arrays, and returns undefined for other
objects; then update both return sites that currently use String(link.title ??
'') to instead call reconstructTitleFromObject(link.title) so callers get a
sensible title or undefined to allow graceful fallback.

---

Nitpick comments:
In `@src/runtime/utils/index.ts`:
- Line 68: Extract the duplicated coercion logic for link.title into a small
local helper (e.g., coerceLinkTitle or formatLinkTitle) and replace both
occurrences of the expression typeof link.title === 'string' ? link.title :
String(link.title ?? '') with a call to that helper; ensure the helper accepts
the link (or title) and returns the same string result so future changes are
centralized and both branches use the single helper implementation.
🪄 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

Run ID: 4914b299-8973-4651-aecd-5563e58ea398

📥 Commits

Reviewing files that changed from the base of the PR and between 55b8793 and 7d889f1.

📒 Files selected for processing (1)
  • src/runtime/utils/index.ts

for (const child of link.children) {
if (child.path === path) {
return link.title
return typeof link.title === 'string' ? link.title : String(link.title ?? '')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Object titles still stringify to "[object Object]" — coercion satisfies the type but not the user-visible bug.

The motivating case from issue #3754 is YAML parsing a title like Getting Started: Basics into an object such as { "Getting Started": "Basics" }. For that input:

  • String({ "Getting Started": "Basics" })"[object Object]"

So while this change does fix the TypeScript return-type violation (and correctly maps undefined/null to ""), the rendered headline in Docus will still display "[object Object]" for the exact scenario this PR cites. The defensive coercion only helps for primitives like number/boolean.

If the goal is to keep this purely as a type-safety guard until the upstream YAML pipeline is fixed, that's reasonable — but consider either:

  1. Reconstructing the original string from the parsed object (best-effort), or
  2. Returning undefined for non-string titles so downstream consumers can fall back to another source instead of rendering garbage.
♻️ Option 1: Best-effort reconstruction of `key: value` titles from YAML-parsed objects

Extract a small helper and use it at both return points:

+function coerceTitle(title: unknown): string {
+  if (typeof title === 'string') return title
+  if (title == null) return ''
+  // YAML may parse "Foo: Bar" as { Foo: "Bar" }; reconstruct best-effort.
+  if (typeof title === 'object') {
+    const entries = Object.entries(title as Record<string, unknown>)
+    if (entries.length === 1) {
+      const [k, v] = entries[0]
+      return `${k}: ${typeof v === 'string' ? v : ''}`.trim()
+    }
+  }
+  return String(title)
+}

Then:

-            return typeof link.title === 'string' ? link.title : String(link.title ?? '')
+            return coerceTitle(link.title)

(applied at both line 68 and line 78)

♻️ Option 2: Return `undefined` so callers can fall back gracefully
-            return typeof link.title === 'string' ? link.title : String(link.title ?? '')
+            return typeof link.title === 'string' ? link.title : undefined

This keeps the function honest about its string | undefined contract and lets consumers decide how to handle missing/malformed titles, rather than rendering "[object Object]".

Also applies to: 78-78

🤖 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 `@src/runtime/utils/index.ts` at line 68, The current coercion returns "[object
Object]" for YAML-parsed object titles; replace the String(...) fallback with a
best-effort reconstruction helper: add a helper function
reconstructTitleFromObject(value: unknown): string | undefined that returns the
original string for primitives, returns "key: value" for single-key objects
(recursively stringifying nested primitives), joins array elements with ", " for
arrays, and returns undefined for other objects; then update both return sites
that currently use String(link.title ?? '') to instead call
reconstructTitleFromObject(link.title) so callers get a sensible title or
undefined to allow graceful fallback.

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.

findPageHeadline() can return a non-string value and surface [object Object] in Docus headers

1 participant