+
0 ? renderedItems.length - 1 : 0}
+ initialTopMostItemIndex={
+ renderedItems.length > 0
+ ? { index: renderedItems.length - 1, align: 'end' }
+ : 0
+ }
startReached={onStartReached}
atBottomStateChange={(atBottom) => setIsAtBottom(atBottom)}
+ scrollerRef={(el) => { scrollerRef.current = el; }}
overscan={isMobile ? 50 : 200}
itemContent={(_, item) => {
if (item.type === 'notice') {
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
index 24163d9..a85af64 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/layout/Header.tsx
@@ -1,10 +1,16 @@
import { memo, useCallback, useState } from "react";
import { Link, useLocation, useParams } from "react-router-dom";
-import { ChevronRight, Home, Settings } from "lucide-react";
+import { ChevronRight, ChevronDown, Home, Settings } from "lucide-react";
import { useProjectLayout } from "@/app/project/layout";
import { useProjectsQuery } from "@/hooks/useProjectsQuery";
import { useOptionalRoom } from "@/contexts/room";
import { RoomSettingsModal } from "@/app/project/channel/RoomSettingsModal";
+import {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+} from "@/components/ui/dropdown-menu";
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -19,7 +25,78 @@ interface BreadcrumbSegment {
fullUuid?: string;
}
-function useBreadcrumbs(): BreadcrumbSegment[] {
+interface BreadcrumbSibling {
+ label: string;
+ path: string;
+}
+
+const ME_NAV_SIBLINGS: BreadcrumbSibling[] = [
+ { label: "Overview", path: "/me" },
+ { label: "Repositories", path: "/me/repositories" },
+ { label: "Projects", path: "/me/projects" },
+ { label: "Activity", path: "/me/activity" },
+ { label: "Chat", path: "/me/chat" },
+ { label: "Stars", path: "/me/stars" },
+ { label: "Following", path: "/me/following" },
+ { label: "Followers", path: "/me/followers" },
+];
+
+function getProjectNavSiblings(projectName: string): BreadcrumbSibling[] {
+ return [
+ { label: "Repository", path: `/${projectName}/repos` },
+ { label: "Issues", path: `/${projectName}/issues` },
+ { label: "Skills", path: `/${projectName}/skills` },
+ { label: "Board", path: `/${projectName}/board` },
+ { label: "Chat", path: `/${projectName}/chat` },
+ { label: "Settings", path: `/${projectName}/settings` },
+ ];
+}
+
+function getRepoTabSiblings(repoBasePath: string): BreadcrumbSibling[] {
+ return [
+ { label: "Code", path: repoBasePath },
+ { label: "Commits", path: `${repoBasePath}/commits` },
+ { label: "Pull Requests", path: `${repoBasePath}/pulls` },
+ { label: "Branches", path: `${repoBasePath}/branches` },
+ { label: "Tags", path: `${repoBasePath}/tags` },
+ { label: "Settings", path: `${repoBasePath}/settings` },
+ ];
+}
+
+function getSegmentSiblings(
+ segment: BreadcrumbSegment,
+ projects: Array<{ name: string; display_name: string }>,
+): BreadcrumbSibling[] | null {
+ if (segment.isLast) return null;
+
+ const parts = segment.path.split("/").filter(Boolean);
+ const depth = parts.length;
+
+ if (depth === 1) {
+ if (parts[0] === "me") return ME_NAV_SIBLINGS;
+ if (projects.length > 1) {
+ return projects.map((p) => ({
+ label: p.display_name || p.name,
+ path: `/${p.name}`,
+ }));
+ }
+ return null;
+ }
+
+ if (depth === 2) {
+ if (parts[0] === "me") return ME_NAV_SIBLINGS;
+ return getProjectNavSiblings(parts[0]);
+ }
+
+ if (depth >= 3 && parts[1] === "repo") {
+ const repoBase = `/${parts[0]}/repo/${parts[2]}`;
+ return getRepoTabSiblings(repoBase);
+ }
+
+ return null;
+}
+
+function useBreadcrumbs() {
const location = useLocation();
const params = useParams<{
projectName?: string;
@@ -65,7 +142,7 @@ function useBreadcrumbs(): BreadcrumbSegment[] {
segments.push({ label, path, isLast, fullUuid });
}
- return segments;
+ return { segments, projects };
}
const TOOLBAR_ICONS = [
@@ -89,7 +166,7 @@ const TOOLBAR_ICONS = [
export const Header = memo(function Header() {
const location = useLocation();
- const segments = useBreadcrumbs();
+ const { segments, projects } = useBreadcrumbs();
const { showMembers, setShowMembers } = useProjectLayout();
const [showSettings, setShowSettings] = useState(false);
const roomContext = useOptionalRoom();
@@ -116,38 +193,62 @@ export const Header = memo(function Header() {
- {segments.map((segment, idx) => (
-
-
- {segment.isLast ? (
- handleCopy(e, segment.fullUuid!) }
- : {})}
- >
- {segment.label}
-
- ) : segment.fullUuid ? (
- handleCopy(e, segment.fullUuid!)}
- >
- {segment.label}
-
- ) : (
-
- {segment.label}
-
- )}
-
- ))}
+ {segments.map((segment, idx) => {
+ const siblings = getSegmentSiblings(segment, projects);
+
+ return (
+
+
+ {segment.isLast ? (
+ handleCopy(e, segment.fullUuid!) }
+ : {})}
+ >
+ {segment.label}
+
+ ) : siblings && siblings.length > 0 ? (
+
+
+
+
+
+ {siblings.map((s) => (
+
+
+ {s.label}
+
+
+ ))}
+
+
+ ) : segment.fullUuid ? (
+ handleCopy(e, segment.fullUuid!)}
+ >
+ {segment.label}
+
+ ) : (
+
+ {segment.label}
+
+ )}
+
+ );
+ })}
diff --git a/src/components/layout/ServerIconRail.tsx b/src/components/layout/ServerIconRail.tsx
index d990f75..1aea400 100644
--- a/src/components/layout/ServerIconRail.tsx
+++ b/src/components/layout/ServerIconRail.tsx
@@ -5,7 +5,7 @@ import {useProjectsQuery} from "@/hooks/useProjectsQuery";
import {Popover, PopoverContent, PopoverTrigger,} from "@/components/ui/popover";
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar";
import {useSettingsModal} from "@/components/settings/SettingsModalContext";
-import {LogOut, Settings, Home, Plus} from "lucide-react";
+import {LogOut, Settings, Home, Plus, Compass} from "lucide-react";
import { CreateProjectModal } from "@/app/me/components/CreateProjectModal";
const AVATAR_COLORS = [
@@ -33,6 +33,7 @@ export const ServerIconRail = memo(function ServerIconRail() {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const isHome = location.pathname.startsWith("/me");
+ const isExplore = location.pathname.startsWith("/explore");
const pathParts = location.pathname.split("/").filter(Boolean);
const currentProjectName = pathParts.length > 0 ? pathParts[0] : "";
@@ -93,6 +94,16 @@ export const ServerIconRail = memo(function ServerIconRail() {
})
)}
+ {/* Explore */}
+
navigate("/explore")}
+ title="Explore Projects"
+ >
+
+
+
{/* Add project */}