From f5056d2e84905f03ca1addae2810a9536b8e270e Mon Sep 17 00:00:00 2001 From: chihlasm Date: Tue, 24 Mar 2026 09:30:58 +0000 Subject: [PATCH] feat: add useBranching hook for branch state management Manages branch list and active branch ID with useState. Exposes loadBranches, createFork, switchBranch, updateStatus, reviveBranch, and sendMessage actions via useCallback. Errors surface via toast notifications. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/hooks/useBranching.ts | 161 +++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 frontend/src/hooks/useBranching.ts diff --git a/frontend/src/hooks/useBranching.ts b/frontend/src/hooks/useBranching.ts new file mode 100644 index 00000000..cf7bbd38 --- /dev/null +++ b/frontend/src/hooks/useBranching.ts @@ -0,0 +1,161 @@ +import { useState, useCallback } from 'react' +import { branchesApi } from '@/api/branches' +import { toast } from '@/lib/toast' +import type { + BranchResponse, + ForkCreateRequest, + ForkPointResponse, + BranchSwitchResponse, + ReviveRequest, + BranchMessageRequest, + BranchMessageResponse, +} from '@/types/branching' + +export interface UseBranching { + // State + branches: BranchResponse[] + activeBranchId: string | null + isLoading: boolean + isProcessing: boolean + + // Actions + loadBranches: (sessionId: string) => Promise + createFork: (sessionId: string, data: ForkCreateRequest) => Promise + switchBranch: (sessionId: string, branchId: string) => Promise + updateStatus: (sessionId: string, branchId: string, status: string, reason?: string) => Promise + reviveBranch: (sessionId: string, branchId: string, data: ReviveRequest) => Promise + sendMessage: (sessionId: string, branchId: string, data: BranchMessageRequest) => Promise +} + +export function useBranching(): UseBranching { + const [branches, setBranches] = useState([]) + const [activeBranchId, setActiveBranchId] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const [isProcessing, setIsProcessing] = useState(false) + + const loadBranches = useCallback(async (sessionId: string) => { + setIsLoading(true) + try { + const result = await branchesApi.getBranches(sessionId) + setBranches(result.branches) + setActiveBranchId(result.active_branch_id) + } catch { + toast.error('Failed to load branches') + } finally { + setIsLoading(false) + } + }, []) + + const createFork = useCallback(async ( + sessionId: string, + data: ForkCreateRequest + ): Promise => { + setIsProcessing(true) + try { + const result = await branchesApi.createFork(sessionId, data) + // Reload branches to reflect the new fork + const updated = await branchesApi.getBranches(sessionId) + setBranches(updated.branches) + setActiveBranchId(updated.active_branch_id) + return result + } catch { + toast.error('Failed to create fork') + return null + } finally { + setIsProcessing(false) + } + }, []) + + const switchBranch = useCallback(async ( + sessionId: string, + branchId: string + ): Promise => { + setIsProcessing(true) + try { + const result = await branchesApi.switchBranch(sessionId, branchId) + setActiveBranchId(result.active_branch_id) + setBranches(prev => + prev.map(b => b.id === result.active_branch_id ? result.branch : b) + ) + return result + } catch { + toast.error('Failed to switch branch') + return null + } finally { + setIsProcessing(false) + } + }, []) + + const updateStatus = useCallback(async ( + sessionId: string, + branchId: string, + status: string, + reason?: string + ) => { + setIsProcessing(true) + try { + await branchesApi.updateBranchStatus(sessionId, branchId, status, reason) + setBranches(prev => + prev.map(b => + b.id === branchId + ? { ...b, status: status as BranchResponse['status'], status_reason: reason ?? b.status_reason } + : b + ) + ) + } catch { + toast.error('Failed to update branch status') + } finally { + setIsProcessing(false) + } + }, []) + + const reviveBranch = useCallback(async ( + sessionId: string, + branchId: string, + data: ReviveRequest + ) => { + setIsProcessing(true) + try { + await branchesApi.reviveBranch(sessionId, branchId, data) + // Reload to get updated statuses + const updated = await branchesApi.getBranches(sessionId) + setBranches(updated.branches) + setActiveBranchId(updated.active_branch_id) + toast.success('Branch revived') + } catch { + toast.error('Failed to revive branch') + } finally { + setIsProcessing(false) + } + }, []) + + const sendMessage = useCallback(async ( + sessionId: string, + branchId: string, + data: BranchMessageRequest + ): Promise => { + setIsProcessing(true) + try { + const result = await branchesApi.sendBranchMessage(sessionId, branchId, data) + return result + } catch { + toast.error('Failed to send message') + return null + } finally { + setIsProcessing(false) + } + }, []) + + return { + branches, + activeBranchId, + isLoading, + isProcessing, + loadBranches, + createFork, + switchBranch, + updateStatus, + reviveBranch, + sendMessage, + } +}