diff --git a/packages/web/src/components/conclusions/ConclusionBrowser.tsx b/packages/web/src/components/conclusions/ConclusionBrowser.tsx index 347ab55..7d38c76 100644 --- a/packages/web/src/components/conclusions/ConclusionBrowser.tsx +++ b/packages/web/src/components/conclusions/ConclusionBrowser.tsx @@ -1,6 +1,6 @@ import { Link, useParams } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; -import { ArrowLeft, Eye, Lightbulb, Plus, Search, Trash2, X } from "lucide-react"; +import { Eye, Lightbulb, Plus, Search, Trash2, X } from "lucide-react"; import { useMemo, useState } from "react"; import { z } from "zod"; import { @@ -10,6 +10,7 @@ import { useQueryConclusions, } from "@/api/queries"; import type { components } from "@/api/schema.d.ts"; +import { Breadcrumb } from "@/components/layout/Breadcrumb"; import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; import { EmptyState } from "@/components/shared/EmptyState"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; @@ -108,15 +109,7 @@ export function ConclusionBrowser() { return (
- - - {mask(workspaceId)} - +
Conclusions diff --git a/packages/web/src/components/layout/Breadcrumb.tsx b/packages/web/src/components/layout/Breadcrumb.tsx new file mode 100644 index 0000000..cbe84b1 --- /dev/null +++ b/packages/web/src/components/layout/Breadcrumb.tsx @@ -0,0 +1,104 @@ +import { Link, useRouter } from "@tanstack/react-router"; +import { ChevronRight } from "lucide-react"; +import { useDemo } from "@/hooks/useDemo"; + +const SECTION_LABELS: Record = { + peers: "Peers", + sessions: "Sessions", + conclusions: "Conclusions", + webhooks: "Webhooks", + chat: "Chat", +}; + +const KNOWN_SECTIONS = new Set(Object.keys(SECTION_LABELS)); + +type Segment = { label: string; href: string | null; mono?: boolean }; + +function buildSegments(pathname: string, mask: (v: string) => string): Segment[] { + if (!pathname.startsWith("/workspaces")) return []; + + const rest = pathname.slice("/workspaces".length); // "" | "/wid" | "/wid/peers" | ... + + if (!rest) return [{ label: "Workspaces", href: null }]; + + const parts = rest.slice(1).split("/"); // ["wid"] | ["wid", "peers"] | ... + const wid = parts[0]; + if (!wid) return [{ label: "Workspaces", href: null }]; + + const segments: Segment[] = [{ label: "Workspaces", href: "/workspaces" }]; + + if (parts.length === 1) { + segments.push({ label: mask(wid), href: null, mono: true }); + return segments; + } + segments.push({ label: mask(wid), href: `/workspaces/${wid}`, mono: true }); + + const section = parts[1]; + if (!section || !KNOWN_SECTIONS.has(section)) return segments; + + if (parts.length === 2) { + segments.push({ label: SECTION_LABELS[section], href: null }); + return segments; + } + segments.push({ label: SECTION_LABELS[section], href: `/workspaces/${wid}/${section}` }); + + const subId = parts[2]; + if (!subId) return segments; + + if (parts.length === 3) { + segments.push({ label: mask(subId), href: null, mono: true }); + return segments; + } + segments.push({ + label: mask(subId), + href: `/workspaces/${wid}/${section}/${subId}`, + mono: true, + }); + + const subSection = parts[3]; + if (subSection && SECTION_LABELS[subSection]) { + segments.push({ label: SECTION_LABELS[subSection], href: null }); + } + + return segments; +} + +export function Breadcrumb() { + const { state } = useRouter(); + const { mask } = useDemo(); + const segments = buildSegments(state.location.pathname, mask); + + if (segments.length <= 1) return null; + + return ( + + ); +} diff --git a/packages/web/src/components/peers/PeerList.tsx b/packages/web/src/components/peers/PeerList.tsx index 800813c..9c3c0ad 100644 --- a/packages/web/src/components/peers/PeerList.tsx +++ b/packages/web/src/components/peers/PeerList.tsx @@ -1,9 +1,10 @@ -import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { useNavigate, useParams } from "@tanstack/react-router"; import { motion, type Variants } from "framer-motion"; -import { ArrowLeft, ChevronRight, Clock, Eye, Users } from "lucide-react"; +import { ChevronRight, Clock, Eye, Users } from "lucide-react"; import { useMemo, useState } from "react"; import { usePeers } from "@/api/queries"; import type { components } from "@/api/schema.d.ts"; +import { Breadcrumb } from "@/components/layout/Breadcrumb"; import { EmptyState } from "@/components/shared/EmptyState"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; import { JsonViewer } from "@/components/shared/JsonViewer"; @@ -105,15 +106,7 @@ export function PeerList() { return (
- - - {mask(workspaceId)} - +
Peers diff --git a/packages/web/src/components/sessions/SessionDetail.tsx b/packages/web/src/components/sessions/SessionDetail.tsx index 8213279..ed453af 100644 --- a/packages/web/src/components/sessions/SessionDetail.tsx +++ b/packages/web/src/components/sessions/SessionDetail.tsx @@ -1,4 +1,4 @@ -import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { useNavigate, useParams } from "@tanstack/react-router"; import { AnimatePresence, motion } from "framer-motion"; import { AlignLeft, Clock, Copy, MessageSquare, Search, Trash2, Users, X } from "lucide-react"; import { useState } from "react"; @@ -15,6 +15,7 @@ import { useSessionSummaries, } from "@/api/queries"; import type { components } from "@/api/schema.d.ts"; +import { Breadcrumb } from "@/components/layout/Breadcrumb"; import { Badge } from "@/components/shared/Badge"; import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; import { JsonViewer } from "@/components/shared/JsonViewer"; @@ -110,23 +111,7 @@ export function SessionDetail() { return (
-
- - {mask(workspaceId)} - - / - - Sessions - -
+
diff --git a/packages/web/src/components/sessions/SessionList.tsx b/packages/web/src/components/sessions/SessionList.tsx index 84172f1..b4d2049 100644 --- a/packages/web/src/components/sessions/SessionList.tsx +++ b/packages/web/src/components/sessions/SessionList.tsx @@ -1,9 +1,10 @@ -import { Link, useNavigate, useParams } from "@tanstack/react-router"; +import { useNavigate, useParams } from "@tanstack/react-router"; import { motion, type Variants } from "framer-motion"; -import { ArrowLeft, ChevronRight, CircleDot, Clock, MessageSquare } from "lucide-react"; +import { ChevronRight, CircleDot, Clock, MessageSquare } from "lucide-react"; import { useMemo, useState } from "react"; import { useSessions } from "@/api/queries"; import type { components } from "@/api/schema.d.ts"; +import { Breadcrumb } from "@/components/layout/Breadcrumb"; import { EmptyState } from "@/components/shared/EmptyState"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; import { Pagination } from "@/components/shared/Pagination"; @@ -66,15 +67,7 @@ export function SessionList() { return (
- - - {mask(workspaceId)} - +
Sessions diff --git a/packages/web/src/components/workspaces/WebhookManager.tsx b/packages/web/src/components/workspaces/WebhookManager.tsx index 2296745..e97c6f9 100644 --- a/packages/web/src/components/workspaces/WebhookManager.tsx +++ b/packages/web/src/components/workspaces/WebhookManager.tsx @@ -1,10 +1,10 @@ -import { Link } from "@tanstack/react-router"; import { open } from "@tauri-apps/plugin-shell"; import { AnimatePresence, motion } from "framer-motion"; -import { ArrowLeft, ExternalLink, Plus, Trash2, Webhook, Zap } from "lucide-react"; +import { ExternalLink, Plus, Trash2, Webhook, Zap } from "lucide-react"; import { useState } from "react"; import { z } from "zod"; import { useCreateWebhook, useDeleteWebhook, useTestWebhook, useWebhooks } from "@/api/queries"; +import { Breadcrumb } from "@/components/layout/Breadcrumb"; import { ConfirmDialog } from "@/components/shared/ConfirmDialog"; import { ErrorAlert } from "@/components/shared/ErrorAlert"; import { Skeleton } from "@/components/shared/Skeleton"; @@ -55,15 +55,7 @@ export function WebhookManager({ workspaceId }: Props) { return (
- - - {mask(workspaceId)} - +