Skip to content

feat: add breadcrumbs and superposition platform links#970

Open
Datron wants to merge 1 commit intomainfrom
improve-navigation
Open

feat: add breadcrumbs and superposition platform links#970
Datron wants to merge 1 commit intomainfrom
improve-navigation

Conversation

@Datron
Copy link
Copy Markdown
Collaborator

@Datron Datron commented Apr 14, 2026

Problem

Navigation backwards is hard in superposition if deeply nested

Solution

Add breadcrumbs so users can see where they are and jump to any previous screen they had visited along the way to the current page

Screen.Recording.2026-04-14.at.11.55.05.AM.mov

Summary by CodeRabbit

  • New Features

    • Introduced breadcrumb navigation that dynamically displays your current location within the app and provides convenient shortcuts to navigate to parent pages and sections, making it easier to explore different areas of the platform.
  • Improvements

    • Enhanced routing and redirect patterns throughout the application to provide a smoother and more intuitive navigation experience when moving between different sections.

@Datron Datron requested a review from a team as a code owner April 14, 2026 06:27
Copilot AI review requested due to automatic review settings April 14, 2026 06:27
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com bot commented Apr 14, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  crates/context_aware_config/src/api/context/handlers.rs  100% smaller
  crates/superposition/src/main.rs  14% smaller
  crates/frontend/src/components.rs  0% smaller
  crates/frontend/src/components/breadcrumbs.rs  0% smaller
  crates/frontend/src/components/side_nav.rs  0% smaller
  crates/frontend/src/hoc/layout.rs  0% smaller
  crates/frontend/src/types.rs  0% smaller

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4701f0cd-ae71-4876-9f36-f2bcdecffe70

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

A breadcrumb navigation component is introduced to the frontend, automatically deriving breadcrumb segments from URL paths and organizational context. The component is integrated into the layout, the side navigation's root link is adjusted, and backend redirect routes are added for URL consistency.

Changes

Cohort / File(s) Summary
Breadcrumb Component Introduction
crates/frontend/src/components/breadcrumbs.rs, crates/frontend/src/types.rs, crates/frontend/src/components.rs
New Breadcrumbs component reads organization and workspace context, parses URL pathname, filters non-breadcrumb segments, generates breadcrumb items with computed href values (with special routing logic), and renders them via BreadcrumbItem. Supporting BreadcrumbSegment type added with label, href, and is_current fields. Module exported via components declaration.
Layout & Navigation Integration
crates/frontend/src/hoc/layout.rs, crates/frontend/src/components/side_nav.rs
Breadcrumbs component rendered in CommonLayout before child content. Side navigation's top "Superposition Platform" link updated to route to organisations instead of admin.
Backend Routing
crates/superposition/src/main.rs
Two additional Actix redirect routes added for trailing-slashless URLs: "/admin/{org_id}""workspaces" and "/admin/{org_id}/{tenant}""default-config".

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Behold! A breadcrumb trail appears,
Following the path through all your frontiers,
Each segment glows with context's care,
Navigation made simple and fair!
"Hop along," the rabbit declares with cheer, 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding a breadcrumbs navigation component and updating the superposition platform link routing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-navigation

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a global breadcrumb trail to the Superposition UI to improve navigation across deeply nested admin/workspace pages, and adjusts platform entry links/redirects to support the new navigation flow.

Changes:

  • Added Actix redirects for org/workspace “root” admin paths to land on workspaces/default-config pages.
  • Introduced a new Breadcrumbs component and injected it into the shared CommonLayout.
  • Updated the SideNav “Superposition Platform” link to point to /admin/organisations.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/superposition/src/main.rs Adds redirects for /admin/{org_id} and /admin/{org_id}/{tenant} paths.
crates/frontend/src/types.rs Introduces BreadcrumbSegment type for breadcrumb rendering.
crates/frontend/src/hoc/layout.rs Renders the new breadcrumbs in CommonLayout.
crates/frontend/src/components/side_nav.rs Updates platform link target to organisations page.
crates/frontend/src/components/breadcrumbs.rs Implements breadcrumb parsing and UI rendering.
crates/frontend/src/components.rs Exposes the new breadcrumbs component module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

/// Builds the breadcrumb trail from the current URL path.
fn build_breadcrumbs(path: &str, base: &str) -> Vec<BreadcrumbSegment> {
// Strip the base prefix if present
let path = path.replace(base, "");
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

let path = path.replace(base, ""); removes all occurrences of base anywhere in the path, not just a leading prefix. If base ever appears later in the URL (or is a short string), this can corrupt the breadcrumb parsing. Prefer stripping only a leading prefix (e.g. a strip_prefix-style approach) and leaving the rest of the path intact.

Suggested change
let path = path.replace(base, "");
let path = path.strip_prefix(base).unwrap_or(path);

Copilot uses AI. Check for mistakes.
const SKIP_SEGMENTS: [&str; 4] = ["admin", "action", "authz", "org-authz"];

/// Maps a URL path segment to a human-readable label.
/// Returns None for segments that should be skipped (like "action").
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The doc comment for segment_to_label says it "Returns None for segments that should be skipped (like "action")", but this function currently always returns Some(...) for any input. Either update the doc to reflect that skipping is handled elsewhere, or implement the skip logic inside segment_to_label and have callers rely on None.

Suggested change
/// Returns None for segments that should be skipped (like "action").
/// Segment skipping is handled by the caller (for example, `build_breadcrumbs`).

Copilot uses AI. Check for mistakes.
Comment on lines +177 to 180
.service(web::redirect("/admin/{org_id}", "workspaces"))
.service(web::redirect("/admin/{org_id}/", "workspaces"))
.service(web::redirect("/admin/{org_id}/{tenant}", "default-config"))
.service(web::redirect("/admin/{org_id}/{tenant}/", "default-config"))
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The redirects for /admin/{org_id} and /admin/{org_id}/{tenant} use relative Location values ("workspaces" / "default-config"). When the incoming URL does not end with a trailing slash (e.g. /admin/123), browsers resolve relative redirects by replacing the last path segment, which can drop {org_id} / {tenant} (e.g. /admin/workspaces). Use an absolute redirect target that preserves the captured params (e.g. construct the full path in a handler), or redirect /admin/{org_id} -> /admin/{org_id}/ first and only then redirect the trailing-slash route to the subpage.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +82
let current = if previous_segments == "/admin" {
"organisations"
} else if previous_segments
.split("/")
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>()
.len()
== 2
{
"workspaces"
} else {
segment
};
format!("{}{}/{}", base, previous_segments, current)
},
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

build_breadcrumbs rewrites the target href for some crumbs based on previous_segments (mapping to "organisations" / "workspaces"). This makes breadcrumb labels (e.g. an org/workspace id) link to a different page than the path segment they represent, and it can prevent users from navigating back to the actual intermediate URL segments. Consider generating each crumb href as the progressively accumulated path up to that segment (relying on the server redirects for /admin/{org_id} and /admin/{org_id}/{workspace}), instead of substituting fixed segments here.

Copilot uses AI. Check for mistakes.
Signed-off-by: datron <Datron@users.noreply.github.com>
@Datron Datron force-pushed the improve-navigation branch from cd29cb9 to 0b86cca Compare April 14, 2026 06:33
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (2)
crates/frontend/src/components/breadcrumbs.rs (2)

68-81: Consider extracting the segment-counting logic for clarity.

The condition at lines 70-75 counts non-empty path segments to determine if we're at the organization level. While functional, this could be more readable.

♻️ Optional: Extract segment count helper
+fn count_path_segments(path: &str) -> usize {
+    path.split('/').filter(|s| !s.is_empty()).count()
+}
+
 fn build_breadcrumbs(path: &str, base: &str) -> Vec<BreadcrumbSegment> {
     // ... existing code ...
                     } else {
                         let current = if previous_segments == "/admin" {
                             "organisations"
-                        } else if previous_segments
-                            .split("/")
-                            .filter(|s| !s.is_empty())
-                            .collect::<Vec<&str>>()
-                            .len()
-                            == 2
-                        {
+                        } else if count_path_segments(&previous_segments) == 2 {
+                            // At org level: /admin/{org_id}
                             "workspaces"
                         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/frontend/src/components/breadcrumbs.rs` around lines 68 - 81, The
inline logic in breadcrumbs.rs that counts non-empty path segments (used in the
if that sets current based on previous_segments) is hard to read; extract it
into a small helper like a private function (e.g., non_empty_segment_count or
count_segments) that accepts &str and returns usize, then replace the long chain
previous_segments.split(...).filter(...).collect::<Vec<&str>>().len() == 2 with
a call to that helper (e.g., count_segments(previous_segments) == 2) and use
that helper when computing current and the format! call so the intent around
previous_segments and current is clearer.

52-55: Minor: Consider using strip_prefix for safer base removal.

String::replace will replace all occurrences of base in the path. If the base prefix happens to appear again in a segment name (unlikely but possible), it could cause unexpected behavior. Using strip_prefix is more precise for this use case.

♻️ Suggested improvement
 fn build_breadcrumbs(path: &str, base: &str) -> Vec<BreadcrumbSegment> {
     // Strip the base prefix if present
-    let path = path.replace(base, "");
+    let path = path.strip_prefix(base).unwrap_or(path);
     let segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/frontend/src/components/breadcrumbs.rs` around lines 52 - 55, The
current build_breadcrumbs function uses path.replace(base, "") which may remove
non-prefix occurrences; change it to use strip_prefix for precise removal: in
build_breadcrumbs(&str, base: &str) call path.strip_prefix(base) and if it
returns Some(stripped) use that stripped &str, otherwise keep the original path,
then continue splitting/filtering into segments; update the local binding that
currently holds the replaced path accordingly so types remain &str and
downstream code (e.g., segments creation) is unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@crates/frontend/src/components/breadcrumbs.rs`:
- Around line 68-81: The inline logic in breadcrumbs.rs that counts non-empty
path segments (used in the if that sets current based on previous_segments) is
hard to read; extract it into a small helper like a private function (e.g.,
non_empty_segment_count or count_segments) that accepts &str and returns usize,
then replace the long chain
previous_segments.split(...).filter(...).collect::<Vec<&str>>().len() == 2 with
a call to that helper (e.g., count_segments(previous_segments) == 2) and use
that helper when computing current and the format! call so the intent around
previous_segments and current is clearer.
- Around line 52-55: The current build_breadcrumbs function uses
path.replace(base, "") which may remove non-prefix occurrences; change it to use
strip_prefix for precise removal: in build_breadcrumbs(&str, base: &str) call
path.strip_prefix(base) and if it returns Some(stripped) use that stripped &str,
otherwise keep the original path, then continue splitting/filtering into
segments; update the local binding that currently holds the replaced path
accordingly so types remain &str and downstream code (e.g., segments creation)
is unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6d6281eb-1d13-4717-a3bf-0c75ca494b99

📥 Commits

Reviewing files that changed from the base of the PR and between a3ede33 and cd29cb9.

📒 Files selected for processing (6)
  • crates/frontend/src/components.rs
  • crates/frontend/src/components/breadcrumbs.rs
  • crates/frontend/src/components/side_nav.rs
  • crates/frontend/src/hoc/layout.rs
  • crates/frontend/src/types.rs
  • crates/superposition/src/main.rs

Comment on lines +13 to +30
"types",
"audit-log",
"default-config",
"experiment-groups",
"compare",
"config",
"dimensions",
"experiments",
"function",
"resolve",
"secrets",
"variables",
"versions",
"webhooks",
"workspaces",
"overrides",
"create",
"edit",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

do we need this list separately ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@ayushjain17 looked into unifying this array string and Resource enum, there are a couple of differences:

  • Resource uses snake_case but all our url segments are kebab-case. I can't change resource I guess because of casbin DB
  • Resource doesn't have some of the title segments I am looking for like Create and Edit, resolve
  • Resource is mostly singular, but all our title segments are plural, eg: Dimension in Resource and dimensions in the URL

Writing code to sync these two would be more work and still fragile. I am thinking of changing app.rs to instead use a different Enum for the path names like default-config, dimensions, etc which can then convert to Resource type if needed. What do you think?

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.

3 participants