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) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-24 09:30:58 +00:00
parent 30d7d6e5a3
commit f5056d2e84

View File

@@ -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<void>
createFork: (sessionId: string, data: ForkCreateRequest) => Promise<ForkPointResponse | null>
switchBranch: (sessionId: string, branchId: string) => Promise<BranchSwitchResponse | null>
updateStatus: (sessionId: string, branchId: string, status: string, reason?: string) => Promise<void>
reviveBranch: (sessionId: string, branchId: string, data: ReviveRequest) => Promise<void>
sendMessage: (sessionId: string, branchId: string, data: BranchMessageRequest) => Promise<BranchMessageResponse | null>
}
export function useBranching(): UseBranching {
const [branches, setBranches] = useState<BranchResponse[]>([])
const [activeBranchId, setActiveBranchId] = useState<string | null>(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<ForkPointResponse | null> => {
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<BranchSwitchResponse | null> => {
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<BranchMessageResponse | null> => {
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,
}
}