Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions docs/app/reflex_docs/templates/docpage/docpage.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Template for documentation pages."""

import functools
from collections.abc import Callable, Collection
from datetime import datetime
from typing import Callable

import reflex as rx
import reflex_components_internal as ui
Expand All @@ -22,6 +22,35 @@
from reflex_site_shared.utils.docpage import right_sidebar_item_highlight
from reflex_site_shared.views.footer import dark_mode_toggle

_REGISTERED_DOC_ROUTES: set[str] = set()


def _normalize_doc_route(path: str) -> str:
"""Normalize a docs route to use leading and trailing slashes."""
route = f"/{path.strip('/')}"
return "/" if route == "/" else f"{route}/"


def _register_doc_route(path: str) -> None:
"""Track a route registered through the docpage template."""
_REGISTERED_DOC_ROUTES.add(_normalize_doc_route(path))


def _resolve_breadcrumb_href(
href: str, registered_routes: Collection[str] | None = None
) -> str:
"""Resolve a generated breadcrumb href to a registered docs route."""
routes = _REGISTERED_DOC_ROUTES if registered_routes is None else registered_routes
route = _normalize_doc_route(href)
if route in routes:
return route

overview_route = _normalize_doc_route(f"{route}overview")
if overview_route in routes:
return overview_route

return href
Comment on lines +48 to +52
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 The fallback branch returns the raw, un-normalized href (e.g. /enterprise with no trailing slash) while every other return in this function returns the fully-normalized route (e.g. /enterprise/). When a path segment has no matching route and no …/overview/ variant, the resulting breadcrumb link will be missing a trailing slash, producing an inconsistent URL relative to all other crumbs. Returning route keeps the output uniform.

Suggested change
overview_route = _normalize_doc_route(f"{route}overview")
if overview_route in routes:
return overview_route
return href
overview_route = _normalize_doc_route(f"{route}overview")
if overview_route in routes:
return overview_route
return route



class FeedbackState(rx.State):
"""Minimal stub for feedback buttons (full implementation removed)."""
Expand Down Expand Up @@ -607,20 +636,16 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
docs_sidebar_drawer,
)

# Split the path into segments, removing 'docs' and capitalizing each segment
segments = [
segment.capitalize()
for segment in path.split("/")
if segment and segment != "docs"
]
# Split the path into segments, removing 'docs'.
segments = [segment for segment in path.split("/") if segment and segment != "docs"]

# Initialize an empty list to store the breadcrumbs and their separators
breadcrumbs = []

# Iteratively build the href for each segment (paths are app-relative, no /docs prefix)
current_path = ""
for i, segment in enumerate(segments):
current_path += f"/{segment.lower()}"
current_path += f"/{segment}"

# Add the breadcrumb item to the list
breadcrumbs.append(
Expand All @@ -629,7 +654,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
class_name="min-h-8 flex items-center text-sm font-[525] text-m-slate-12 dark:text-m-slate-3 last:text-m-slate-7 dark:last:text-m-slate-6 hover:text-primary-10 dark:hover:text-primary-9"
+ (" truncate" if i == len(segments) - 1 else ""),
underline="none",
href=current_path,
href=_resolve_breadcrumb_href(current_path),
)
)

Expand Down Expand Up @@ -713,6 +738,7 @@ def docpage(contents: Callable[[], Route]) -> Route:
The final route with the template applied.
"""
path = get_path(contents, "reflex-docs/pages") if set_path is None else set_path
_register_doc_route(path)

title = contents.__name__.replace("_", " ").title() if t is None else t

Expand Down
32 changes: 32 additions & 0 deletions docs/app/tests/test_breadcrumbs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Tests for docs breadcrumbs."""

import importlib
import sys
from pathlib import Path

import reflex as rx

sys.path.append(str(Path(__file__).resolve().parent.parent))


def test_enterprise_parent_breadcrumb_uses_overview_route(monkeypatch):
"""Parent breadcrumbs should link to a real overview route when needed."""
docpage_module = importlib.import_module("reflex_docs.templates.docpage.docpage")
monkeypatch.setattr(
docpage_module,
"_REGISTERED_DOC_ROUTES",
{
"/enterprise/overview/",
"/enterprise/ag-grid/",
"/enterprise/ag-grid/pivot-mode/",
},
raising=False,
)

rendered = str(
docpage_module.breadcrumb("/enterprise/ag-grid/pivot-mode/", rx.box())
)

assert 'to:"/enterprise/overview/"' in rendered
assert 'to:"/enterprise/ag-grid/"' in rendered
assert 'to:"/enterprise/ag-grid/pivot-mode/"' in rendered
Comment on lines +26 to +32
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Fragile serialization-based assertions

The assertions match against Reflex's internal component serialization string (e.g. 'to:"/enterprise/overview/"'). If Reflex ever changes how it stringifies component props — which is an implementation detail, not a public contract — these assertions will silently break even when the breadcrumb logic is perfectly correct. A less brittle alternative would be to call _resolve_breadcrumb_href directly for the intermediate path segments, or to inspect the rendered component tree's props rather than the raw str() output.

Loading