fix(web): scope all backend calls to the active repo, not always the first

The root cause of four related issues: `repoRef.current` was never set during
initial server connection — only `switchRepo()` set it. Every backend call
(queries, search, grep, readFile) passed `undefined` as the repo parameter,
so the server's `resolveRepo()` fell back to `repos[0]`.

Changes:
- Expose `setCurrentRepo` from useAppState and call it in `handleServerConnect`
- Use `result.repoInfo.name` (canonical server name) instead of path parsing
- Parse `?project=` URL param during auto-connect, pass it to `connectToServer`
- Update URL with `?project=` on repo switch so F5/bookmarks work
- Pass `projectName` as `repo` in CodeReferencesPanel's `readFile` calls

Closes #139, closes #634, closes #635, closes #636

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Abhigyan Patwari 2026-04-04 07:13:59 +05:30
parent 5c4fca21c3
commit a6c66f9777
3 changed files with 67 additions and 35 deletions

View file

@ -45,17 +45,21 @@ const AppContent = () => {
availableRepos,
setAvailableRepos,
switchRepo,
setCurrentRepo,
} = useAppState();
const graphCanvasRef = useRef<GraphCanvasHandle>(null);
const handleServerConnect = useCallback(
async (result: ConnectResult): Promise<void> => {
// Extract project name from repoPath
// Use the canonical repo name from the server response so all subsequent
// backend calls (queries, search, grep, readFile) scope to this repo.
const repoName = result.repoInfo.name;
const repoPath = result.repoInfo.repoPath ?? result.repoInfo.path;
const parts = (repoPath || '').split('/').filter((p) => p && !p.startsWith('.'));
const projectName = parts[parts.length - 1] || parts[0] || 'server-project';
const projectName =
repoName || repoPath?.split('/').filter(Boolean).pop() || 'server-project';
setProjectName(projectName);
setCurrentRepo(projectName);
// Build KnowledgeGraph from server data for visualization
const graph = createKnowledgeGraph();
@ -80,10 +84,18 @@ const AppContent = () => {
console.warn('Failed to initialize agent:', err);
}
},
[setViewMode, setGraph, setProjectName, initializeAgent, startEmbeddingsWithFallback],
[
setViewMode,
setGraph,
setProjectName,
setCurrentRepo,
initializeAgent,
startEmbeddingsWithFallback,
],
);
// Auto-connect when ?server query param is present (bookmarkable shortcut)
// Auto-connect when ?server query param is present (bookmarkable shortcut).
// Also reads ?project= to connect to a specific repo.
const autoConnectRan = useRef(false);
useEffect(() => {
if (autoConnectRan.current) return;
@ -91,7 +103,10 @@ const AppContent = () => {
if (!params.has('server')) return;
autoConnectRan.current = true;
// Clean the URL so a refresh won't re-trigger
const serverUrl = params.get('server') || window.location.origin;
const projectParam = params.get('project') || undefined;
// Clean the URL so a refresh won't re-trigger auto-connect
const cleanUrl = window.location.pathname + window.location.hash;
window.history.replaceState(null, '', cleanUrl);
@ -103,36 +118,39 @@ const AppContent = () => {
});
setViewMode('loading');
const serverUrl = params.get('server') || window.location.origin;
const baseUrl = normalizeServerUrl(serverUrl);
connectToServer(serverUrl, (phase, downloaded, total) => {
if (phase === 'validating') {
setProgress({
phase: 'extracting',
percent: 5,
message: 'Connecting to server...',
detail: 'Validating server',
});
} else if (phase === 'downloading') {
const pct = total ? Math.round((downloaded / total) * 90) + 5 : 50;
const mb = (downloaded / (1024 * 1024)).toFixed(1);
setProgress({
phase: 'extracting',
percent: pct,
message: 'Downloading graph...',
detail: `${mb} MB downloaded`,
});
} else if (phase === 'extracting') {
setProgress({
phase: 'extracting',
percent: 97,
message: 'Processing...',
detail: 'Extracting file contents',
});
}
})
connectToServer(
serverUrl,
(phase, downloaded, total) => {
if (phase === 'validating') {
setProgress({
phase: 'extracting',
percent: 5,
message: 'Connecting to server...',
detail: 'Validating server',
});
} else if (phase === 'downloading') {
const pct = total ? Math.round((downloaded / total) * 90) + 5 : 50;
const mb = (downloaded / (1024 * 1024)).toFixed(1);
setProgress({
phase: 'extracting',
percent: pct,
message: 'Downloading graph...',
detail: `${mb} MB downloaded`,
});
} else if (phase === 'extracting') {
setProgress({
phase: 'extracting',
percent: 97,
message: 'Processing...',
detail: 'Extracting file contents',
});
}
},
undefined,
projectParam,
)
.then(async (result) => {
await handleServerConnect(result);
setProgress(null);

View file

@ -54,6 +54,7 @@ export const CodeReferencesPanel = ({ onFocusNode }: CodeReferencesPanelProps) =
clearCodeReferences,
setSelectedNode,
codeReferenceFocus,
projectName,
} = useAppState();
const nodeById = useMemo(() => {
@ -223,10 +224,11 @@ export const CodeReferencesPanel = ({ onFocusNode }: CodeReferencesPanelProps) =
const isWholeFile = selectedIsFile || startLine === undefined;
const options = isWholeFile
? undefined
? { repo: projectName }
: {
startLine: Math.max(0, startLine - CONTEXT_LINES),
endLine: (endLine ?? startLine) + CONTEXT_LINES,
repo: projectName,
};
readFile(selectedFilePath, options)
@ -251,6 +253,7 @@ export const CodeReferencesPanel = ({ onFocusNode }: CodeReferencesPanelProps) =
selectedNode?.properties?.startLine,
selectedNode?.properties?.endLine,
selectedIsFile,
projectName,
]);
// Scroll to the selected node's startLine after content loads

View file

@ -145,6 +145,7 @@ interface AppState {
availableRepos: BackendRepo[];
setAvailableRepos: (repos: BackendRepo[]) => void;
switchRepo: (repoName: string) => Promise<void>;
setCurrentRepo: (repoName: string) => void;
// Worker API (shared across app)
runQuery: (cypher: string) => Promise<any[]>;
@ -456,6 +457,10 @@ const AppStateProviderInner = ({ children }: { children: ReactNode }) => {
// Backend client — direct HTTP calls (no Worker/Comlink)
const repoRef = useRef<string | undefined>(undefined);
const setCurrentRepo = useCallback((repoName: string) => {
repoRef.current = repoName;
}, []);
const runQuery = useCallback(async (cypher: string): Promise<any[]> => {
return backendRunQuery(cypher, repoRef.current);
}, []);
@ -1077,6 +1082,11 @@ const AppStateProviderInner = ({ children }: { children: ReactNode }) => {
setProjectName(pName);
repoRef.current = pName;
// Update URL so F5 / bookmarks open the correct repo
const url = new URL(window.location.href);
url.searchParams.set('project', pName);
window.history.replaceState(null, '', url.toString());
const newGraph = createKnowledgeGraph();
for (const node of result.nodes) newGraph.addNode(node);
for (const rel of result.relationships) newGraph.addRelationship(rel);
@ -1219,6 +1229,7 @@ const AppStateProviderInner = ({ children }: { children: ReactNode }) => {
availableRepos,
setAvailableRepos,
switchRepo,
setCurrentRepo,
runQuery,
isDatabaseReady,
// Embedding state and methods