Skip to content

Commit 3653b8e

Browse files
ENG-9556: Remove url for breadcrumbs with no associated doc page (reflex-dev#6591)
* ENG-9556: Remove url for breadcrumbs with no associated doc page * update --------- Co-authored-by: Farhan Ali Raza <62690310+FarhanAliRaza@users.noreply.github.com>
1 parent 6f8db6b commit 3653b8e

2 files changed

Lines changed: 85 additions & 12 deletions

File tree

docs/app/reflex_docs/templates/docpage/docpage.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424

2525
_REGISTERED_DOC_ROUTES: set[str] = set()
2626

27+
# Title-cased breadcrumb labels that should be displayed as acronyms.
28+
_BREADCRUMB_LABEL_OVERRIDES: dict[str, str] = {
29+
"Ai": "AI",
30+
"Api": "API",
31+
"Sdk": "SDK",
32+
"Cli": "CLI",
33+
"Css": "CSS",
34+
}
35+
2736

2837
def _normalize_doc_route(path: str) -> str:
2938
"""Normalize a docs route to use leading and trailing slashes."""
@@ -38,8 +47,24 @@ def _register_doc_route(path: str) -> None:
3847

3948
def _resolve_breadcrumb_href(
4049
href: str, registered_routes: Collection[str] | None = None
41-
) -> str:
42-
"""Resolve a generated breadcrumb href to a registered docs route."""
50+
) -> str | None:
51+
"""Resolve a generated breadcrumb href to a registered docs route.
52+
53+
Breadcrumbs are built from path segments, but intermediate segments (e.g.
54+
``/ai`` or ``/hosting``) are often just categories with no page of their
55+
own. This returns the matching route, preferring an ``overview`` child when
56+
the bare path is not itself a page, or ``None`` when no registered route
57+
exists so the caller can render the segment as non-clickable text instead of
58+
a broken link.
59+
60+
Args:
61+
href: The generated, app-relative breadcrumb href (no ``/docs`` prefix).
62+
registered_routes: Routes to match against. Defaults to the routes
63+
registered through the docpage template.
64+
65+
Returns:
66+
The resolved route, or ``None`` if no registered route matches.
67+
"""
4368
routes = _REGISTERED_DOC_ROUTES if registered_routes is None else registered_routes
4469
route = _normalize_doc_route(href)
4570
if route in routes:
@@ -49,7 +74,7 @@ def _resolve_breadcrumb_href(
4974
if overview_route in routes:
5075
return overview_route
5176

52-
return href
77+
return None
5378

5479

5580
class FeedbackState(rx.State):
@@ -678,17 +703,31 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
678703
for i, segment in enumerate(segments):
679704
current_path += f"/{segment}"
680705

681-
# Add the breadcrumb item to the list
682-
breadcrumbs.append(
683-
rx.el.a(
684-
to_title_case(to_snake_case(segment), sep=" "),
685-
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"
686-
+ (" truncate" if i == len(segments) - 1 else ""),
687-
underline="none",
688-
href=_resolve_breadcrumb_href(current_path),
689-
)
706+
label = to_title_case(to_snake_case(segment), sep=" ")
707+
label = _BREADCRUMB_LABEL_OVERRIDES.get(label, label)
708+
base_class = ui.cn(
709+
"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",
710+
"truncate" if i == len(segments) - 1 else "",
690711
)
691712

713+
# Category segments (e.g. /ai, /hosting) often have no page of their own.
714+
# Render those as plain text so the breadcrumb doesn't link to a 404.
715+
href = _resolve_breadcrumb_href(current_path)
716+
if href is None:
717+
breadcrumbs.append(rx.el.span(label, class_name=base_class))
718+
else:
719+
breadcrumbs.append(
720+
rx.el.a(
721+
label,
722+
class_name=ui.cn(
723+
base_class,
724+
"hover:text-primary-10 dark:hover:text-primary-9",
725+
),
726+
underline="none",
727+
href=href,
728+
)
729+
)
730+
692731
# If it's not the last segment, add a separator
693732
if i < len(segments) - 1:
694733
breadcrumbs.append(

docs/app/tests/test_breadcrumbs.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,37 @@ def test_enterprise_parent_breadcrumb_uses_overview_route(monkeypatch):
2626
assert 'to:"/enterprise/overview/"' in rendered
2727
assert 'to:"/enterprise/ag-grid/"' in rendered
2828
assert 'to:"/enterprise/ag-grid/pivot-mode/"' in rendered
29+
30+
31+
def test_missing_parent_breadcrumb_is_not_clickable(monkeypatch):
32+
"""Breadcrumb segments without a real route should not be clickable links."""
33+
docpage_module = importlib.import_module("reflex_docs.templates.docpage.docpage")
34+
# Only the leaf page is a registered route; the "ai" and "ai/overview"
35+
# parents have no page and no overview child (mirrors /docs/ai 404s).
36+
monkeypatch.setattr(
37+
docpage_module,
38+
"_REGISTERED_DOC_ROUTES",
39+
{"/ai/overview/some-feature/"},
40+
raising=False,
41+
)
42+
43+
rendered = str(docpage_module.breadcrumb("/ai/overview/some-feature/", rx.box()))
44+
45+
# The leaf resolves to a real route and stays clickable.
46+
assert 'to:"/ai/overview/some-feature/"' in rendered
47+
# The missing parents must not render as links to broken URLs.
48+
assert 'to:"/ai/"' not in rendered
49+
assert 'to:"/ai/overview/"' not in rendered
50+
# Their labels are still shown as plain text ("ai" renders as "AI").
51+
assert "AI" in rendered
52+
assert "Overview" in rendered
53+
54+
55+
def test_resolve_breadcrumb_href_returns_none_for_missing_route():
56+
"""A path with no registered route or overview child resolves to None."""
57+
docpage_module = importlib.import_module("reflex_docs.templates.docpage.docpage")
58+
59+
assert (
60+
docpage_module._resolve_breadcrumb_href("/hosting", {"/hosting/deploy/"})
61+
is None
62+
)

0 commit comments

Comments
 (0)