From 05143fd2773cd53e2ce54e9b0ea3a5553a027142 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Tue, 28 Apr 2026 16:57:01 -0700 Subject: [PATCH] Fix AI docs sidebar and breadcrumb --- docs/ai_builder/overview/best_practices.md | 2 +- docs/app/reflex_docs/docgen_pipeline.py | 31 ++++++++------ .../pages/docs_landing/views/ai_builder.py | 2 +- .../reflex_docs/templates/docpage/docpage.py | 10 ++++- .../templates/docpage/sidebar/sidebar.py | 23 ++++------ .../docpage/sidebar/sidebar_items/ai.py | 2 +- docs/app/reflex_docs/views/docs_navbar.py | 22 ++++++++-- docs/app/tests/test_routes.py | 42 +++++++++++++++++++ 8 files changed, 100 insertions(+), 34 deletions(-) diff --git a/docs/ai_builder/overview/best_practices.md b/docs/ai_builder/overview/best_practices.md index 761c3a26b95..7e7a1a6e952 100644 --- a/docs/ai_builder/overview/best_practices.md +++ b/docs/ai_builder/overview/best_practices.md @@ -1,6 +1,6 @@ # Reflex Build: Best Practices -> A comprehensive guide to working effectively with AI Builder. This guide outlines how to get the most reliable and efficient results when working with the AI Builder inside Reflex Build. The key to success is clarity, structure, and iteration. +A comprehensive guide to working effectively with AI Builder. This guide outlines how to get the most reliable and efficient results when working with the AI Builder inside Reflex Build. The key to success is clarity, structure, and iteration. --- diff --git a/docs/app/reflex_docs/docgen_pipeline.py b/docs/app/reflex_docs/docgen_pipeline.py index 8a567b9fe65..5587dcfa7ce 100644 --- a/docs/app/reflex_docs/docgen_pipeline.py +++ b/docs/app/reflex_docs/docgen_pipeline.py @@ -335,8 +335,11 @@ def transform_list_item(self, item: ListItem) -> rx.Component: def quote(self, block: QuoteBlock) -> rx.Component: children = [self.transform_block(b) for b in block.children] return rx.box( - *children, - class_name="border-l-[3px] border-slate-4 pl-6 mt-2 mb-6", + rx.box( + *children, + padding_left="2rem", + ), + class_name="border-l-[3px] border-slate-4 mt-2 mb-6", ) def table(self, block: TableBlock) -> rx.Component: @@ -654,18 +657,22 @@ def _render_quote_directive(self, block: DirectiveBlock) -> rx.Component: role = text.split(":", 1)[1].strip() return rx.box( - rx.text( - '"', - *quote_parts, - '"', - class_name="text-slate-11 font-base italic", - ), rx.box( - rx.text(name, class_name="text-slate-11 font-base"), - rx.text(role, class_name="text-slate-10 font-base"), - class_name="flex flex-col gap-0.5", + rx.text( + '"', + *quote_parts, + '"', + class_name="text-slate-11 font-base italic", + ), + rx.box( + rx.text(name, class_name="text-slate-11 font-base"), + rx.text(role, class_name="text-slate-10 font-base"), + class_name="flex flex-col gap-0.5", + ), + padding_left="2rem", + class_name="flex flex-col gap-4", ), - class_name="flex flex-col gap-4 border-l-[3px] border-slate-4 pl-6 mt-2 mb-6", + class_name="flex flex-col gap-4 border-l-[3px] border-slate-4 mt-2 mb-6", ) def _render_tabs(self, block: DirectiveBlock) -> rx.Component: diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py index 2a9fb84e9a4..7052e328016 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py @@ -163,7 +163,7 @@ def ai_builder_section() -> rx.Component: src=f"{REFLEX_ASSETS_CDN}docs/{rx.color_mode_cond('light', 'dark')}/getting_started.svg", class_name="w-full h-auto pb-8", ), - href=ai_builder_pages.overview.best_practices.path, + href=ai_builder_pages.overview.what_is_reflex_build.path, ), card( title="Integrations", diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 06c0915752b..0de57448d62 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -23,6 +23,14 @@ from reflex_site_shared.utils.docpage import right_sidebar_item_highlight from reflex_site_shared.views.footer import dark_mode_toggle +ACRONYMS = {"ai": "AI", "api": "API", "cli": "CLI", "ide": "IDE", "mcp": "MCP"} + + +def format_title_with_acronyms(text: str) -> str: + """Format title-cased text while preserving known acronyms.""" + title = to_title_case(to_snake_case(text), sep=" ") + return " ".join(ACRONYMS.get(word.lower(), word) for word in title.split(" ")) + class FeedbackState(rx.State): """Minimal stub for feedback buttons (full implementation removed).""" @@ -586,7 +594,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N # Add the breadcrumb item to the list breadcrumbs.append( rx.el.a( - to_title_case(to_snake_case(segment), sep=" "), + format_title_with_acronyms(segment), 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", diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index a41dfe03d0f..98461f97e48 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -243,25 +243,20 @@ def calculate_index(sidebar_items, url: str) -> list[int]: sidebar_items = ( sidebar_items if isinstance(sidebar_items, list) else [sidebar_items] ) - index_list = [] if not url: - return index_list + return [] url = url.rstrip("/") + "/" - for item in sidebar_items: - item.link = item.link.rstrip("/") + "/" - sub = 0 for i, item in enumerate(sidebar_items): - if not item.children: - sub += 1 - if item.link == url: - return [i - sub] + item_link = item.link.rstrip("/") + "/" if item.link else "" + if item_link == url: + return [i] index = calculate_index(item.children, url) if index: - return [i - sub, *index] + return [i, *index] - return index_list + return [] def append_to_items(items, flat_items): @@ -434,7 +429,7 @@ def sidebar_comp( from reflex_docs.pages.docs.library import library from reflex_docs.pages.docs.recipes_overview import overview - _path = rx.State.router.page.path + _path = rx.State.router.page.raw_path _is_docs_hosting = _path.startswith("/docs/hosting/") | _path.startswith( "/hosting/" ) @@ -458,7 +453,7 @@ def sidebar_comp( rx.el.ul( sidebar_category( "AI Builder", - ai_builder_pages.overview.best_practices.path, + ai_builder_pages.overview.what_is_reflex_build.path, "bot", 0, ), @@ -546,7 +541,7 @@ def sidebar_comp( rx.el.ul( create_sidebar_section( "Overview", - ai_builder_pages.overview.best_practices.path, + ai_builder_pages.overview.what_is_reflex_build.path, ai_builder_overview_items, ai_builder_overview_index, url, diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py index b8cf7288a9b..1929b79f616 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py @@ -9,8 +9,8 @@ def get_sidebar_items_ai_builder_overview(): create_item( "Overview", children=[ - ai_builder.overview.best_practices, ai_builder.overview.what_is_reflex_build, + ai_builder.overview.best_practices, ai_builder.overview.tutorial, ai_builder.overview.templates, ], diff --git a/docs/app/reflex_docs/views/docs_navbar.py b/docs/app/reflex_docs/views/docs_navbar.py index 13d92039ba4..c66fa4b2394 100644 --- a/docs/app/reflex_docs/views/docs_navbar.py +++ b/docs/app/reflex_docs/views/docs_navbar.py @@ -65,7 +65,7 @@ def logo() -> rx.Component: def menu_item(text: str, href: str, active_str: str = "") -> rx.Component: - router_path = rx.State.router.page.path + router_path = rx.State.router.page.raw_path active_cn = "shadow-[inset_0_-1px_0_0_var(--primary-10)] [&_button]:text-primary-10 [&_div]:text-primary-10" # For paths starting with "/" (like Start), use exact match @@ -73,11 +73,21 @@ def menu_item(text: str, href: str, active_str: str = "") -> rx.Component: # For other segments (like "ai"), use contains if active_str.startswith("/"): if active_str == "/": - active = (router_path == "/") | (router_path == "/index") + active = ( + (router_path == "/") + | (router_path == "/index") + | (router_path == "/docs") + | (router_path == "/docs/") + ) else: active = router_path == active_str elif active_str == "framework": - is_overview = (router_path == "/") | (router_path == "/index") + is_overview = ( + (router_path == "/") + | (router_path == "/index") + | (router_path == "/docs") + | (router_path == "/docs/") + ) is_ai_builder = router_path.startswith("/ai/") | router_path.startswith( "/docs/ai/" ) @@ -110,7 +120,11 @@ def navigation_menu() -> rx.Component: return ui.navigation_menu.root( ui.navigation_menu.list( menu_item("Overview", "/", "/"), - menu_item("Build with AI", ai_builder.overview.best_practices.path, "ai"), + menu_item( + "Build with AI", + ai_builder.overview.what_is_reflex_build.path, + "ai", + ), menu_item("Framework", getting_started.introduction.path, "framework"), menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"), class_name="flex flex-row items-center gap-2 m-0 h-full list-none", diff --git a/docs/app/tests/test_routes.py b/docs/app/tests/test_routes.py index a75eff34751..25a5bb2c70c 100644 --- a/docs/app/tests/test_routes.py +++ b/docs/app/tests/test_routes.py @@ -43,3 +43,45 @@ def test_ai_builder_routes_use_ai_prefix(routes_fixture): assert "/ai-builder/integrations/ai-onboarding/" not in paths assert "/ai-builder/integrations/mcp-overview/" not in paths assert "/ai-builder/integrations/skills/" not in paths + + +def test_ai_overview_sidebar_order_and_active_index(): + from reflex_docs.pages.docs import ai_builder + from reflex_docs.templates.docpage.sidebar.sidebar import calculate_index + from reflex_docs.templates.docpage.sidebar.sidebar_items.ai import ( + ai_builder_overview_items, + ) + + overview_children = ai_builder_overview_items[0].children + + assert overview_children[0].link == ai_builder.overview.what_is_reflex_build.path + assert overview_children[1].link == ai_builder.overview.best_practices.path + assert ( + calculate_index( + ai_builder_overview_items, + ai_builder.overview.best_practices.path, + ) + == [0, 1] + ) + + +def test_sidebar_index_matches_rendered_item_positions(): + from reflex_docs.templates.docpage.sidebar.sidebar import calculate_index + from reflex_docs.templates.docpage.sidebar.state import SideBarItem + + items = [ + SideBarItem(names="Intro", link="/intro/"), + SideBarItem( + names="Group", + children=[SideBarItem(names="Target", link="/target/")], + ), + ] + + assert calculate_index(items, "/target/") == [1, 0] + + +def test_breadcrumb_titles_preserve_acronyms(): + from reflex_docs.templates.docpage.docpage import format_title_with_acronyms + + assert format_title_with_acronyms("ai") == "AI" + assert format_title_with_acronyms("mcp-overview") == "MCP Overview"