@@ -314,6 +333,7 @@ export function Dashboard() {
const router = useRouter()
const { data, loading, error, refetch } = useDashboard()
const scope = useTweaks((s) => s.scope)
+ const activeRepo = useTweaks((s) => s.activeRepo)
// Separate from the global workspace `scope` — this one partitions
// the caveats card into first-party code ("yours"), test fixtures,
// and vendored dependencies. "yours" is the default because the raw
@@ -547,12 +567,17 @@ export function Dashboard() {
Repositories
- {snap.stats.repos} indexed · {scope === 'federated' ? 'federated' : 'single repo'} view
+ {scope === 'single' && activeRepo
+ ? `scoped to ${activeRepo}`
+ : `${snap.stats.repos} indexed · federated view`}
- {snap.repos.slice(0, 6).map((r) => (
+ {(scope === 'single' && activeRepo
+ ? snap.repos.filter((r) => r.id === activeRepo)
+ : snap.repos.slice(0, 6)
+ ).map((r) => (
))}
diff --git a/src/components/graph/GraphView.tsx b/src/components/graph/GraphView.tsx
index 2b3f78d..e6e134f 100644
--- a/src/components/graph/GraphView.tsx
+++ b/src/components/graph/GraphView.tsx
@@ -98,18 +98,28 @@ function RepoFilterPanel({
export function GraphView() {
const showMinimap = useTweaks((s) => s.showMinimap)
+ const scope = useTweaks((s) => s.scope)
+ const activeRepo = useTweaks((s) => s.activeRepo)
+ const setTweak = useTweaks((s) => s.set)
const { data: repos, loading, error } = useRepos()
const { data: dash } = useDashboard()
- const { data: graph, loading: graphLoading, error: graphError } = useGraph()
+ const repoList = repos ?? []
+ const effectiveRepo = scope === 'single' ? (activeRepo || repoList[0]?.id) : undefined
+ const { data: graph, loading: graphLoading, error: graphError } = useGraph(
+ effectiveRepo ? { repo: effectiveRepo } : undefined,
+ )
const [mode, setMode] = useState
('constellation')
const [filtered, setFiltered] = useState>(new Set())
const [filterKinds, setFilterKinds] = useState>(new Set())
useEffect(() => {
- if (repos && filtered.size === 0) {
+ if (!repos) return
+ if (effectiveRepo) {
+ setFiltered(new Set([effectiveRepo]))
+ } else if (filtered.size === 0) {
setFiltered(new Set(repos.map((r) => r.id)))
}
- }, [repos, filtered.size])
+ }, [repos, effectiveRepo, filtered.size])
// Default kinds: everything except `file`. File nodes inflate the graph
// without adding useful topology, so they start off; user can opt in.
@@ -125,7 +135,11 @@ export function GraphView() {
else n.add(id)
setFiltered(n)
}
- const only = (id: string) => setFiltered(new Set([id]))
+ const only = (id: string) => {
+ setFiltered(new Set([id]))
+ setTweak('scope', 'single')
+ setTweak('activeRepo', id)
+ }
const toggleKind = (kind: string) => {
const n = new Set(filterKinds)
if (n.has(kind)) n.delete(kind)
@@ -133,7 +147,6 @@ export function GraphView() {
setFilterKinds(n)
}
- const repoList = repos ?? []
const visibleRepos = repoList.filter((r) => !filtered.size || filtered.has(r.id))
const kinds = dash?.kinds ?? []
@@ -145,9 +158,18 @@ export function GraphView() {
{loading || graphLoading
? 'Loading graph…'
- : `${filtered.size} of ${repoList.length} repos · ${(graph?.nodes.length ?? dash?.stats.total_nodes ?? 0).toLocaleString()} nodes · ${(graph?.edges.length ?? dash?.stats.total_edges ?? 0).toLocaleString()} edges`}
+ : effectiveRepo
+ ? `Single repo · ${effectiveRepo} · ${(graph?.nodes.length ?? 0).toLocaleString()} nodes · ${(graph?.edges.length ?? 0).toLocaleString()} edges`
+ : `${filtered.size} of ${repoList.length} repos · ${(graph?.nodes.length ?? dash?.stats.total_nodes ?? 0).toLocaleString()} nodes · ${(graph?.edges.length ?? dash?.stats.total_edges ?? 0).toLocaleString()} edges`}
+ {effectiveRepo && (
+
+ setTweak('scope', 'federated')}>
+ Show all repos
+
+
+ )}
diff --git a/src/components/pages/ServicesView.tsx b/src/components/pages/ServicesView.tsx
index 4cc3e00..376165e 100644
--- a/src/components/pages/ServicesView.tsx
+++ b/src/components/pages/ServicesView.tsx
@@ -1,22 +1,47 @@
'use client'
+import { useRouter } from 'next/navigation'
import { Icon } from '@/components/primitives/Icon'
import { StackedBar } from '@/components/primitives/Charts'
import { useRepos } from '@/lib/hooks'
+import { useTweaks } from '@/lib/tweaks'
export function ServicesView() {
+ const router = useRouter()
const { data, loading, error, refetch } = useRepos()
- const repos = data ?? []
+ const scope = useTweaks((s) => s.scope)
+ const activeRepo = useTweaks((s) => s.activeRepo)
+ const set = useTweaks((s) => s.set)
+ const allRepos = data ?? []
+ const repos = scope === 'single' && activeRepo
+ ? allRepos.filter((r) => r.id === activeRepo)
+ : allRepos
+
+ const drillIn = (id: string) => {
+ set('scope', 'single')
+ set('activeRepo', id)
+ router.push('/graph')
+ }
+
return (
<>
Services
- {loading ? 'Loading…' : `${repos.length} indexed services · click to drill in`}
+ {loading
+ ? 'Loading…'
+ : scope === 'single' && activeRepo
+ ? `Scoped to ${activeRepo} · click to open its graph`
+ : `${repos.length} indexed services · click to drill in`}
+ {scope === 'single' && activeRepo && (
+
set('scope', 'federated')}>
+ Show all
+
+ )}
Refresh
@@ -38,7 +63,20 @@ export function ServicesView() {
{repos.map((r) => (
-
+
drillIn(r.id)}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault()
+ drillIn(r.id)
+ }
+ }}
+ >
diff --git a/src/lib/api.ts b/src/lib/api.ts
index e38845a..5dcb2a4 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -132,13 +132,15 @@ export const api = {
},
// Fetches the full step list + files for a single process. Uses the
- // `get_processes` MCP tool with the `id` parameter so the response
- // includes every step's node ID — list endpoints deliberately omit
- // these to keep the summary light.
+ // `analyze` facade's `processes` kind with the `id` parameter so the
+ // response includes every step's node ID — list endpoints deliberately
+ // omit these to keep the summary light. The facade routes
+ // analyze(kind=processes) to the get_processes handler without needing
+ // the legacy tool promoted into the live registry (core/defer surface).
processDetail: async (id: string): Promise
=> {
if (!id) return null
try {
- return await callToolJSON('get_processes', { id })
+ return await callToolJSON('analyze', { kind: 'processes', id })
} catch { return null }
},
diff --git a/src/lib/tweaks.ts b/src/lib/tweaks.ts
index 3a4f441..0cab8c6 100644
--- a/src/lib/tweaks.ts
+++ b/src/lib/tweaks.ts
@@ -13,6 +13,7 @@ export type Tweaks = {
theme: Theme
layout: Layout
scope: Scope
+ activeRepo: string | null
caveats: CaveatDensity
graphStyle: 'constellation' | 'tree' | 'sankey' | '3d'
density: Density
@@ -23,6 +24,7 @@ const initial: Tweaks = {
theme: 'ink',
layout: 'tri',
scope: 'federated',
+ activeRepo: null,
caveats: 'inline',
graphStyle: 'constellation',
density: 'comfortable',
@@ -50,6 +52,7 @@ export const useTweaks = create()(
theme: s.theme,
layout: s.layout,
scope: s.scope,
+ activeRepo: s.activeRepo,
caveats: s.caveats,
graphStyle: s.graphStyle,
density: s.density,