From 93e08e5c8ea97e888a5620c247512c017f9f3493 Mon Sep 17 00:00:00 2001 From: mawkone Date: Sun, 14 Jun 2026 13:47:08 -0700 Subject: [PATCH] feat(codebase): auto-select the best entry point file on load so preview isn't empty --- .../project/[projectId]/(home)/code/page.tsx | 3 + .../components/project/gitea-file-tree.tsx | 66 ++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/code/page.tsx b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/code/page.tsx index ea3d467..ab0d629 100644 --- a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/code/page.tsx +++ b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/code/page.tsx @@ -183,6 +183,9 @@ export default function CodeTab() { 0 && cb.id === codebases[0].id + } selectedPath={ selection?.type === "file" && selection.codebaseId === cb.id diff --git a/vibn-frontend/components/project/gitea-file-tree.tsx b/vibn-frontend/components/project/gitea-file-tree.tsx index 89be813..36da62b 100644 --- a/vibn-frontend/components/project/gitea-file-tree.tsx +++ b/vibn-frontend/components/project/gitea-file-tree.tsx @@ -9,7 +9,7 @@ * Gitea's web UI on click. */ -import { useEffect, useState, useCallback } from "react"; +import { useEffect, useState, useCallback, useRef } from "react"; import { Loader2, AlertCircle } from "lucide-react"; import { Tree, Folder, File } from "@/components/ui/file-tree"; import { THEME } from "@/components/project/dashboard-ui"; @@ -36,6 +36,8 @@ interface GiteaFileTreeProps { onSelectFile?: (path: string) => void; /** Path of the currently-selected file, used to highlight the row. */ selectedPath?: string; + /** If true, automatically selects the best available file (e.g. page.tsx, package.json) on mount. */ + autoSelect?: boolean; } // ── In-memory cache to persist tree state across tab navigations ── @@ -48,13 +50,45 @@ const treeCache: Record< } > = {}; +function pickBestFile(paths: string[]): string | undefined { + const PREFERRED = [ + "src/app/page.tsx", + "src/app/page.jsx", + "app/page.tsx", + "app/page.jsx", + "src/pages/index.tsx", + "src/pages/index.jsx", + "pages/index.tsx", + "pages/index.jsx", + "src/App.tsx", + "src/App.jsx", + "src/main.tsx", + "src/main.jsx", + "src/index.ts", + "src/index.js", + "package.json", + "README.md", + ]; + for (const p of PREFERRED) { + if (paths.includes(p)) return p; + } + const fallbackExts = [".tsx", ".ts", ".jsx", ".js", ".json"]; + for (const ext of fallbackExts) { + const found = paths.find((p) => p.endsWith(ext)); + if (found) return found; + } + return paths[0]; +} + export function GiteaFileTree({ projectId, rootPath, onSelectFile, selectedPath, + autoSelect, }: GiteaFileTreeProps) { const cacheKey = `${projectId}::${rootPath}`; + const autoSelectedRef = useRef(false); const [rootItems, setRootItems] = useState(() => { return treeCache[cacheKey]?.rootItems ?? null; @@ -101,7 +135,22 @@ export function GiteaFileTree({ // Load root whenever projectId or rootPath changes useEffect(() => { // If we already loaded this from cache on mount, skip fetching again - if (treeCache[cacheKey]) return; + if (treeCache[cacheKey]) { + if (autoSelect && !autoSelectedRef.current && onSelectFile) { + autoSelectedRef.current = true; + const cached = treeCache[cacheKey]; + const allItems = [ + ...cached.rootItems, + ...Object.values(cached.childrenByPath).flat(), + ]; + const filePaths = allItems + .filter((i) => i.type === "file") + .map((i) => i.path); + const best = pickBestFile(filePaths); + if (best) onSelectFile(best); + } + return; + } let cancelled = false; setLoading(true); @@ -184,6 +233,19 @@ export function GiteaFileTree({ ); } + if (autoSelect && !autoSelectedRef.current && onSelectFile) { + autoSelectedRef.current = true; + const allItems = [ + ...items, + ...Object.values(newChildrenByPath).flat(), + ]; + const filePaths = allItems + .filter((i) => i.type === "file") + .map((i) => i.path); + const best = pickBestFile(filePaths); + if (best) onSelectFile(best); + } + if (cancelled) return; setRootItems(items);