diff --git a/.gitignore b/.gitignore index b721bff..716ca25 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +.gortex/ diff --git a/src/components/chrome/StatusBar.tsx b/src/components/chrome/StatusBar.tsx index 8fff231..edbf2d7 100644 --- a/src/components/chrome/StatusBar.tsx +++ b/src/components/chrome/StatusBar.tsx @@ -6,6 +6,7 @@ import { useDashboard } from '@/lib/hooks' export function StatusBar() { const scope = useTweaks((s) => s.scope) + const activeRepo = useTweaks((s) => s.activeRepo) const { data, error } = useDashboard() const nodes = data?.stats.total_nodes const edges = data?.stats.total_edges @@ -33,7 +34,9 @@ export function StatusBar() { · - scope {scope === 'federated' ? 'all repos' : 'single'} + scope + {scope === 'federated' ? 'all repos' : activeRepo ? `single · ${activeRepo}` : 'single'} + {version} diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index 9b72f9a..b97f01f 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -65,6 +65,8 @@ function Kpi({ } function RepoCard({ r }: { r: Repo }) { + const router = useRouter() + const set = useTweaks((s) => s.set) const kinds = [ { label: 'functions', value: r.funcs, color: 'var(--k-function)' }, { label: 'methods', value: r.methods, color: 'var(--k-method)' }, @@ -72,8 +74,25 @@ function RepoCard({ r }: { r: Repo }) { { label: 'interfaces', value: r.interfaces, color: 'var(--k-interface)' }, { label: 'variables', value: r.vars, color: 'var(--k-variable)' }, ] + const drillIn = () => { + set('scope', 'single') + set('activeRepo', r.id) + router.push('/graph') + } return ( -
+
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + drillIn() + } + }} + >
@@ -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 && ( +
+ +
+ )}
{(error || graphError) && (
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 && ( + + )} @@ -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,