import { defineStore } from 'pinia'; import { ref } from 'vue'; import type { CreateWorktreeRequest, CreateWorktreeResponse, DiscoverReposResponse, RepoResponse, ReposListResponse, RepoSummary, WorktreeSummary, WorktreesListResponse, } from '@arboretum/shared'; import { api } from '../lib/api'; import { wsClient, type WorktreeEvent } from '../lib/ws-client'; export const useWorktreesStore = defineStore('worktrees', () => { const repos = ref([]); const worktrees = ref([]); const loading = ref(false); const loadError = ref(null); let unsubscribe: (() => void) | null = null; function upsertRepo(repo: RepoSummary): void { const idx = repos.value.findIndex((r) => r.id === repo.id); if (idx >= 0) repos.value.splice(idx, 1, repo); else repos.value.push(repo); } function removeRepoLocal(id: string): void { repos.value = repos.value.filter((r) => r.id !== id); worktrees.value = worktrees.value.filter((w) => w.repoId !== id); } function upsertWorktree(wt: WorktreeSummary): void { const idx = worktrees.value.findIndex((w) => w.path === wt.path); if (idx >= 0) worktrees.value.splice(idx, 1, wt); else worktrees.value.push(wt); } function removeWorktreeLocal(path: string): void { worktrees.value = worktrees.value.filter((w) => w.path !== path); } function onEvent(e: WorktreeEvent): void { if (e.type === 'repo_update') upsertRepo(e.repo); else if (e.type === 'repo_removed') removeRepoLocal(e.repoId); else if (e.type === 'worktree_update') upsertWorktree(e.worktree); else removeWorktreeLocal(e.path); } function worktreesForRepo(repoId: string): WorktreeSummary[] { return worktrees.value .filter((w) => w.repoId === repoId) .sort((a, b) => Number(b.isMain) - Number(a.isMain) || a.path.localeCompare(b.path)); } async function fetchAll(): Promise { loading.value = true; loadError.value = null; try { const [r, w] = await Promise.all([ api.get('/api/v1/repos'), api.get('/api/v1/worktrees'), ]); repos.value = r.repos; worktrees.value = w.worktrees; } catch (err) { loadError.value = err instanceof Error ? err.message : String(err); } finally { loading.value = false; } } async function refreshRepoWorktrees(repoId: string): Promise { const w = await api.get(`/api/v1/repos/${repoId}/worktrees`); for (const wt of w.worktrees) upsertWorktree(wt); } async function addRepo(path: string, label?: string): Promise { const res = await api.post('/api/v1/repos', { path, ...(label ? { label } : {}) }); upsertRepo(res.repo); await refreshRepoWorktrees(res.repo.id); return res.repo; } async function removeRepo(id: string): Promise { await api.delete<{ ok: true }>(`/api/v1/repos/${id}`); removeRepoLocal(id); } /** Masque/ré-affiche un repo (persiste `hidden` côté serveur ; émis aussi par WS). */ async function setHidden(id: string, hidden: boolean): Promise { const res = await api.patch(`/api/v1/repos/${id}`, { hidden }); upsertRepo(res.repo); } /** Lance un scan de découverte ; les nouveaux repos arrivent par WS, on resynchronise par sûreté. */ async function discover(): Promise { const res = await api.post('/api/v1/repos/discover'); await fetchAll(); return res; } async function createWorktree(repoId: string, req: CreateWorktreeRequest): Promise { const res = await api.post(`/api/v1/repos/${repoId}/worktrees`, req); upsertWorktree(res.worktree); return res; } async function deleteWorktree(repoId: string, path: string, force: boolean): Promise { await api.delete<{ ok: true }>(`/api/v1/repos/${repoId}/worktrees?path=${encodeURIComponent(path)}&force=${force}`); removeWorktreeLocal(path); } async function prune(repoId: string): Promise { await api.post<{ ok: true }>(`/api/v1/repos/${repoId}/worktrees/prune`); await refreshRepoWorktrees(repoId); } function startRealtime(): void { unsubscribe ??= wsClient.subscribeWorktrees(onEvent); } function stopRealtime(): void { unsubscribe?.(); unsubscribe = null; } return { repos, worktrees, loading, loadError, worktreesForRepo, fetchAll, addRepo, removeRepo, setHidden, discover, refreshRepoWorktrees, createWorktree, deleteWorktree, prune, startRealtime, stopRealtime, }; });