/** * `GitPanel` — feature component for the Git panel (L8). * * Shows the working-tree status grouped by Staged / Unstaged, a commit form, * branch management with checkout, and the recent commit log. * * Pure presentation: all behaviour comes from {@link useGit}. Styled with * `@/shared`; no inline styles. */ import { useState } from "react"; import { Button, Input, Panel, Spinner, cn } from "@/shared"; import { useGit } from "./useGit"; import { buildFileTree, type GitTreeNode } from "./gitTree"; /** * Recursively renders a changed-files tree. Directories are static rows; file * leaves carry the stage/unstage action (its `aria-label` uses the full path). */ function FileTree({ nodes, depth, action, onAction, busy, }: { nodes: GitTreeNode[]; depth: number; action: "stage" | "unstage"; onAction: (path: string) => void; busy: boolean; }) { return ( ); } export interface GitPanelProps { /** The project whose git state to manage. */ projectId: string; } export function GitPanel({ projectId }: GitPanelProps) { const vm = useGit(projectId); const [commitMessage, setCommitMessage] = useState(""); const [newBranch, setNewBranch] = useState(""); const staged = vm.files.filter((f) => f.staged); const unstaged = vm.files.filter((f) => !f.staged); const canCommit = commitMessage.trim().length > 0 && staged.length > 0 && !vm.busy; async function handleCommit(e: React.FormEvent) { e.preventDefault(); if (!canCommit) return; await vm.commit(commitMessage.trim()); setCommitMessage(""); } async function handleCheckout(branch: string) { await vm.checkout(branch); } async function handleNewBranch(e: React.FormEvent) { e.preventDefault(); const b = newBranch.trim(); if (!b || vm.busy) return; await vm.checkout(b); setNewBranch(""); } return ( {vm.error && (

{vm.error}

)} {/* ── Changes ── */}

Changes

{vm.busy && }
{vm.files.length === 0 ? (

No changes.

) : (
{/* Staged — shown as a file tree. */} {staged.length > 0 && (

Staged

void vm.unstage(p)} busy={vm.busy} />
)} {/* Unstaged — shown as a file tree. */} {unstaged.length > 0 && (

Unstaged

void vm.stage(p)} busy={vm.busy} />
)}
)}
{/* ── Commit ── */}

Commit

setCommitMessage(e.target.value)} />
{/* ── Branches ── */}

Branches

Current:{" "} {vm.branches.current ?? "(none)"}

{vm.branches.branches.length > 0 && ( )}
setNewBranch(e.target.value)} className="flex-1" />
{/* ── Log ── */}

Log

{vm.log.length === 0 ? (

No commits yet.

) : ( )}
); }