From b63c90186e7d5dda1fa409499d10744408cad6a6 Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Thu, 8 Jan 2026 13:29:31 +0000 Subject: [PATCH] feat: Show timeout indicator for repository selector (Vibe Kanban) (#1510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Done. I've added a 5-second delay to the `list_git_repos` endpoint at `crates/server/src/routes/filesystem.rs:46`. This will let you see: - "Searching for repositories..." for the first 2 seconds - "Still searching... (2s)", "Still searching... (3s)", etc. after that - The "This is taking longer than usual" message after 3 seconds Remember to remove this delay (marked with `TODO`) once you're done testing! * Done, the delay has been removed. * All checks pass. The i18n script now passes with no new violations introduced. * feat: Add timeout indicator to repository selector - Shows progressive feedback when repo search takes longer than expected - Displays "no repos found" message when search completes empty - Added translations for all 6 locales (EN, ES, JA, KO, ZH-Hans, ZH-Hant) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- .../dialogs/shared/RepoPickerDialog.tsx | 61 ++++++++++++++++++- frontend/src/i18n/locales/en/projects.json | 7 +++ frontend/src/i18n/locales/es/projects.json | 7 +++ frontend/src/i18n/locales/ja/projects.json | 7 +++ frontend/src/i18n/locales/ko/projects.json | 7 +++ .../src/i18n/locales/zh-Hans/projects.json | 7 +++ .../src/i18n/locales/zh-Hant/projects.json | 7 +++ 7 files changed, 100 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/dialogs/shared/RepoPickerDialog.tsx b/frontend/src/components/dialogs/shared/RepoPickerDialog.tsx index a1658862..6cda0717 100644 --- a/frontend/src/components/dialogs/shared/RepoPickerDialog.tsx +++ b/frontend/src/components/dialogs/shared/RepoPickerDialog.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -38,6 +39,7 @@ const RepoPickerDialogImpl = NiceModal.create( title = 'Select Repository', description = 'Choose or create a git repository', }) => { + const { t } = useTranslation('projects'); const modal = useModal(); const [stage, setStage] = useState('options'); const [error, setError] = useState(''); @@ -47,6 +49,8 @@ const RepoPickerDialogImpl = NiceModal.create( const [allRepos, setAllRepos] = useState([]); const [reposLoading, setReposLoading] = useState(false); const [showMoreRepos, setShowMoreRepos] = useState(false); + const [loadingDuration, setLoadingDuration] = useState(0); + const [hasSearched, setHasSearched] = useState(false); // Stage: new const [repoName, setRepoName] = useState(''); @@ -60,12 +64,15 @@ const RepoPickerDialogImpl = NiceModal.create( setShowMoreRepos(false); setRepoName(''); setParentPath(''); + setLoadingDuration(0); + setHasSearched(false); } }, [modal.visible]); const loadRecentRepos = useCallback(async () => { setReposLoading(true); setError(''); + setLoadingDuration(0); try { const repos = await fileSystemApi.listGitRepos(); setAllRepos(repos); @@ -74,14 +81,33 @@ const RepoPickerDialogImpl = NiceModal.create( console.error('Failed to load repos:', err); } finally { setReposLoading(false); + setHasSearched(true); } }, []); useEffect(() => { - if (stage === 'existing' && allRepos.length === 0 && !reposLoading) { + if ( + stage === 'existing' && + allRepos.length === 0 && + !reposLoading && + !hasSearched + ) { loadRecentRepos(); } - }, [stage, allRepos.length, reposLoading, loadRecentRepos]); + }, [stage, allRepos.length, reposLoading, hasSearched, loadRecentRepos]); + + // Track loading duration to show timeout message + useEffect(() => { + if (!reposLoading) { + return; + } + + const interval = setInterval(() => { + setLoadingDuration((prev) => prev + 1); + }, 1000); + + return () => clearInterval(interval); + }, [reposLoading]); const registerAndReturn = async (path: string) => { setIsWorking(true); @@ -220,9 +246,18 @@ const RepoPickerDialogImpl = NiceModal.create(
- Loading repositories... + {loadingDuration < 2 + ? t('repoSearch.searching') + : t('repoSearch.stillSearching', { + seconds: loadingDuration, + })}
+ {loadingDuration >= 3 && ( +
+ {t('repoSearch.takingLonger')} +
+ )}
)} @@ -269,6 +304,26 @@ const RepoPickerDialogImpl = NiceModal.create( )} + {/* No repos found state */} + {!reposLoading && + hasSearched && + allRepos.length === 0 && + !error && ( +
+
+ +
+
+ {t('repoSearch.noReposFound')} +
+
+ {t('repoSearch.browseHint')} +
+
+
+
+ )} +
!isWorking && handleBrowseForRepo()} diff --git a/frontend/src/i18n/locales/en/projects.json b/frontend/src/i18n/locales/en/projects.json index e8179e45..9f951ae4 100644 --- a/frontend/src/i18n/locales/en/projects.json +++ b/frontend/src/i18n/locales/en/projects.json @@ -4,6 +4,13 @@ "createProject": "Create Project", "linkToOrganization": "Link to Remote Project", "loading": "Loading projects...", + "repoSearch": { + "searching": "Searching for repositories...", + "stillSearching": "Still searching... ({{seconds}}s)", + "takingLonger": "This is taking longer than usual. You can browse manually below.", + "noReposFound": "No repositories found in common locations.", + "browseHint": "Use the option below to browse for a repository." + }, "errors": { "fetchFailed": "Failed to fetch projects", "deleteFailed": "Failed to delete project" diff --git a/frontend/src/i18n/locales/es/projects.json b/frontend/src/i18n/locales/es/projects.json index 7a8431af..9b92f813 100644 --- a/frontend/src/i18n/locales/es/projects.json +++ b/frontend/src/i18n/locales/es/projects.json @@ -4,6 +4,13 @@ "createProject": "Crear Proyecto", "linkToOrganization": "Vincular a Proyecto Remoto", "loading": "Cargando proyectos...", + "repoSearch": { + "searching": "Buscando repositorios...", + "stillSearching": "Aún buscando... ({{seconds}}s)", + "takingLonger": "Esto está tardando más de lo habitual. Puedes buscar manualmente abajo.", + "noReposFound": "No se encontraron repositorios en ubicaciones comunes.", + "browseHint": "Usa la opción de abajo para buscar un repositorio." + }, "errors": { "fetchFailed": "Error al cargar proyectos", "deleteFailed": "Error al eliminar el proyecto" diff --git a/frontend/src/i18n/locales/ja/projects.json b/frontend/src/i18n/locales/ja/projects.json index fb548905..33283127 100644 --- a/frontend/src/i18n/locales/ja/projects.json +++ b/frontend/src/i18n/locales/ja/projects.json @@ -4,6 +4,13 @@ "createProject": "プロジェクトを作成", "linkToOrganization": "リモートプロジェクトにリンク", "loading": "プロジェクトを読み込み中...", + "repoSearch": { + "searching": "リポジトリを検索中...", + "stillSearching": "検索中... ({{seconds}}秒)", + "takingLonger": "通常より時間がかかっています。下から手動で選択できます。", + "noReposFound": "一般的な場所にリポジトリが見つかりませんでした。", + "browseHint": "下のオプションを使用してリポジトリを参照してください。" + }, "errors": { "fetchFailed": "プロジェクトの取得に失敗しました", "deleteFailed": "プロジェクトの削除に失敗しました" diff --git a/frontend/src/i18n/locales/ko/projects.json b/frontend/src/i18n/locales/ko/projects.json index a2c52ce9..7789e472 100644 --- a/frontend/src/i18n/locales/ko/projects.json +++ b/frontend/src/i18n/locales/ko/projects.json @@ -4,6 +4,13 @@ "createProject": "프로젝트 생성", "linkToOrganization": "원격 프로젝트에 연결", "loading": "프로젝트 로딩 중...", + "repoSearch": { + "searching": "저장소 검색 중...", + "stillSearching": "검색 중... ({{seconds}}초)", + "takingLonger": "평소보다 시간이 오래 걸리고 있습니다. 아래에서 수동으로 찾아볼 수 있습니다.", + "noReposFound": "일반적인 위치에서 저장소를 찾을 수 없습니다.", + "browseHint": "아래 옵션을 사용하여 저장소를 찾아보세요." + }, "errors": { "fetchFailed": "프로젝트를 불러오지 못했습니다", "deleteFailed": "프로젝트 삭제에 실패했습니다" diff --git a/frontend/src/i18n/locales/zh-Hans/projects.json b/frontend/src/i18n/locales/zh-Hans/projects.json index 64f78825..0aadc6f6 100644 --- a/frontend/src/i18n/locales/zh-Hans/projects.json +++ b/frontend/src/i18n/locales/zh-Hans/projects.json @@ -4,6 +4,13 @@ "createProject": "创建项目", "linkToOrganization": "链接到远程项目", "loading": "加载项目中...", + "repoSearch": { + "searching": "正在搜索仓库...", + "stillSearching": "仍在搜索... ({{seconds}}秒)", + "takingLonger": "搜索时间比平时长。您可以在下方手动浏览。", + "noReposFound": "在常见位置未找到仓库。", + "browseHint": "使用下方选项浏览仓库。" + }, "errors": { "fetchFailed": "获取项目失败", "deleteFailed": "删除项目失败" diff --git a/frontend/src/i18n/locales/zh-Hant/projects.json b/frontend/src/i18n/locales/zh-Hant/projects.json index f63f388f..5815e7ea 100644 --- a/frontend/src/i18n/locales/zh-Hant/projects.json +++ b/frontend/src/i18n/locales/zh-Hant/projects.json @@ -4,6 +4,13 @@ "createProject": "建立專案", "linkToOrganization": "連結到遠端專案", "loading": "載入專案中...", + "repoSearch": { + "searching": "正在搜尋儲存庫...", + "stillSearching": "仍在搜尋... ({{seconds}}秒)", + "takingLonger": "搜尋時間比平時長。您可以在下方手動瀏覽。", + "noReposFound": "在常見位置未找到儲存庫。", + "browseHint": "使用下方選項瀏覽儲存庫。" + }, "errors": { "fetchFailed": "取得專案失敗", "deleteFailed": "刪除專案失敗"