mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[21e195cd] Panel-wide UI standardization and usability pass (#194)
* [4c179e3a] Add git pull, fetch, and rebase backend endpoints (#190) * [f966f772] feat(git): add pull, fetch, and rebase endpoints with integration tests (#185) - Add GitPullRequest/Response, GitFetchRequest/Response, GitRebaseRequest/Response schemas - Add GitService.pull(), fetch(), and rebase() methods using _network_git_timeout() - Add POST /api/git/pull, /api/git/fetch, /api/git/rebase route handlers - Rebase detects conflicts via git diff --name-only --diff-filter=U and aborts cleanly - Integration tests cover success path and GitCommandError→500 for all three endpoints - Rebase conflict test verifies conflict=True with populated conflicted_files list Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> * [26e2b7af] test(git): add AsyncMock unit tests for rebase_onto_base conflict-state handling (#186) New test_git_rebase.py covers three branches of rebase_onto_base: - success path: rebase exits 0, returns rebased status, abort never called - conflict path: non-zero exit → diff → abort → returns conflict+files - resilience: both rebase and abort exit non-zero, still returns conflict dict without exception All tests use AsyncMock with side_effect sequences to mock _run_git at the service-method level. Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> * [551b1dbf] Panel-wide frontend UI standardization and page fixes (#193) * [1ec787b2] feat(panel): design-system sweep — full-width layouts, scrollbar fix, Secretary button, component audit (#188) - Settings page: remove max-w-3xl, wrap cards in grid-cols-1 lg:grid-cols-2 two-column layout - AI Providers page: remove max-w-5xl so AIRoutingCard fills available width - Journals AgentList: replace ScrollArea with overflow-y-auto div to eliminate nested scrollbar - Secretary chat input: add items-stretch to flex row so Send/Start button matches Textarea height - Component audit: replace all raw <button>/<input>/hand-rolled badge spans outside components/ui/ with canonical Button, Checkbox, Badge variants across 15 files: - ai-routing-card.tsx: ModeButton → Button, checkbox → Checkbox, badge spans → Badge - self-hosted-section.tsx: eye-toggle → Button ghost icon-sm, badge spans → Badge - journals/agent-item.tsx, communications/channel-item.tsx → Button ghost - kb-search-bar.tsx, kb-filters.tsx → Button ghost - kb-category-nav.tsx, git-log-panel.tsx → Button ghost - communications/page.tsx (channel + group lists) → Button ghost - projects/project-table.tsx, products/product-table.tsx → Button link - git-branch-panel.tsx (local + remote lists) → Button ghost - tasks/dependency-selector.tsx: Button ghost + Checkbox for visual indicator - tasks/task-table.tsx: sortable header + expand toggle → Button ghost - business/goals-tab.tsx: hidden button → Button Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> * [435b37b4] feat(metrics,notifications): URL-persisted tab state, semantic chart colors, humanized counts (#187) - Notifications page: replace useState with useSearchParams/useRouter for ?tab= URL parameter (all/unread/pending, default: unread); Suspense wrapper with skeleton fallback for SSR compatibility. - Metrics page: split into Performance tab (Velocity + Task Status + Agent Status + Team Health) and Token Usage tab (TokenUsageCostsSection) with ?tab= URL parameter (default: performance); Suspense wrapper; Refresh button moved inside PerformanceTabContent; humanizeCount() helper applies K/M suffixes to all MetricCard numeric values >= 1000. - Chart components (usage-time-series, agent-usage, team-usage, model-donut): replace var(--chart-N) CSS vars with explicit semantic hex colors — #3b82f6 blue for informational, #f59e0b amber for warning/pending, #22c55e green for success/healthy, #ef4444 red for error/blocked, #a855f7 purple for supplemental. pnpm lint and pnpm typecheck pass with zero new errors. Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> * [ccd256f4] Kanban mobile viewport: 375px layout, column navigation, 44px touch targets (#191) * [ccd256f4] feat(kanban): mobile 375px layout with column navigator and 44px touch targets - KanbanBoard: add activeColumnIndex state + mobile prev/next column navigator (lg:hidden); existing horizontal-scroll layout hidden on mobile (hidden lg:flex). Desktop DnD behavior unchanged. - KanbanColumn: add optional className prop (cn-based) so mobile view can pass w-full/sm:w-full to fill the viewport. - KanbanCard: bump all action buttons to min-h-11 (44px) touch targets (Assign, Pass, Fail, Move-forward). * [ccd256f4] fix(kanban): change breakpoint from lg to sm for mobile/desktop layout switch AC3 requires >=640px viewport to show multi-column layout (sm: breakpoint). Previous impl used lg: (1024px), leaving 640-1023px in single-column mode. Change: - Mobile navigator div: lg:hidden → sm:hidden - Desktop multi-column div: hidden lg:flex → hidden sm:flex At <640px: single-column with prev/next navigator (375px mobile use case). At >=640px: full horizontal-scroll multi-column layout (per AC3). DnD behavior and all other layout unchanged. --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> * [23f02af4] Agents page On-Demand section + Board composition; Overview Quick Actions visibility + Team Health Intake/Secretary (#189) * [23f02af4] feat(agents,overview): On-Demand section, Board composition fix, Intake/Secretary in Quick Actions + Team Health - agent-definitions.ts: remove AgentRole.MAIN_PM from getBoardAgents (Main PM has its own dedicated section; including it there was redundant). Add getOnDemandAgents() that catches agents not in any standard team (board/main_pm/backend/frontend/ux_ui/marketing) and not a standard cell role — surfaces prompter/intake agents that the API may return. - agents/page.tsx: import getOnDemandAgents; add a conditional 'On-Demand Agents' AgentGrid section (only rendered when the API returns at least one matching agent, e.g. the Intake interviewer). - quick-actions-bar.tsx: add 'Task Intake' button (→/prompter, Sparkles icon) and 'Secretary' button (→/business?tab=secretary, Bot icon) alongside existing quick actions so operators can reach on-demand agents from the Overview in one click. - team-health-cards.tsx: add OnDemandAgentCard sub-component (link card with On-Demand badge) and render static cards for 'Task Intake' and 'Secretary' appended after the API-driven TeamHealthCard list, giving them equal visual presence in the Team Health section. pnpm lint and pnpm typecheck pass with zero new errors. * [23f02af4] fix(agents,overview): QA revision — enum entries, QuickActions placement, On-Demand title, Board PR_REVIEWER AC3: types/index.ts AgentRole enum adds PR_REVIEWER, PROMPTER, SECRETARY. agent-selector.tsx ROLE_LABELS exhaustive Record updated accordingly. AC4: command-center.tsx QuickActionsBar moved to after Team Health section, before CEO Approval Queue and data-heavy grid rows — visible without scrolling on a 900px viewport. AC1: agents/page.tsx On-Demand AgentGrid title fixed to 'On-Demand' (was 'On-Demand Agents' in prior commit). AC2: agent-definitions.ts getBoardAgents adds explicit PR_REVIEWER inclusion and uses inclusion-based getOnDemandAgents (PROMPTER|SECRETARY roles). AC5: team-health-cards.tsx static OnDemandAgentCard implementation refined with correct fallback rendering when no API team data. AC6: pnpm lint and pnpm typecheck (src only) pass with zero new errors. --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> * [b1c59206] Git page: Pull, Fetch, Rebase buttons wired to backend; Rebase destructive confirmation dialog (#192) * [b1c59206] feat(git): add Pull, Fetch, Rebase operations to Git page with destructive confirmation dialog for Rebase - Add GitPullRequest/Response, GitFetchRequest/Response, GitRebaseRequest/Response types - Add gitApi.pull(), gitApi.fetch(), gitApi.rebase() with mock stubs for /git/pull, /git/fetch, /git/rebase - Add useGitPull, useGitFetch, useGitRebase mutation hooks with cache invalidation; exported via useGitOperations - Add Pull (Download icon), Fetch (RefreshCcw icon), Rebase (GitGraph icon) buttons to GitActionsPanel - Rebase button triggers AlertDialog with destructive confirmation before calling API - Wire handlePull, handleFetch, handleRebase handlers in git-browser.tsx with toast feedback * [b1c59206] fix(git): add destructive styling and branch name to Rebase AlertDialog - Add className='border-destructive bg-destructive/5' to AlertDialogContent so the dialog container has the required red-tinted styling (AC3) - Update AlertDialogDescription to interpolate status?.current_branch so the dialog body explicitly names the branch being rebased (AC3) --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> * [3f305ed9] Frontend: Fix git control contract, complete Secretary restyling, and apply polish (CEO revision) (#199) * [72de8a65] fix(git): correct Pull/Fetch/Rebase types, API mocks, request fields, and toast handlers (#197) - types/git.ts: GitPullResponse and GitFetchResponse now have current_branch, has_changes, staged_files, unstaged_files, untracked_files, ahead, behind (matching backend GitStatusResponse); removed nonexistent commits_received/ refs_updated/remote fields - types/git.ts: GitRebaseRequest now uses target_branch: string (not onto?: string); GitRebaseResponse now has conflict: boolean and conflicted_files: string[] (removed branch/onto/commits_rebased); task_id made optional on all three request types - lib/api/git.ts: Updated mock returns for pull/fetch/rebase to match new types - git-actions-panel.tsx: onRebase prop now (targetBranch: string) => void; Rebase AlertDialog now contains an Input for target_branch; AlertDialogAction disabled when targetBranch empty and passes the value to onRebase - git-browser.tsx: handlePull and handleFetch toast references result.current_branch; handleRebase accepts targetBranch, sends target_branch in payload, toasts result.conflict and result.conflicted_files; no 'manual' task_id for any pull/fetch/rebase operation Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> * [be6a17fc] feat(ui): design-system polish — chart tokens, KB aria-label, Kanban touch targets (#196) - kb-search-bar.tsx: add aria-label="Clear search" to the clear (X) button - model-usage-donut.tsx: replace hex CHART_COLORS with var(--chart-1)…var(--chart-5) - usage-time-series-chart.tsx: replace hex stopColor/stroke with var(--chart-1)/var(--chart-2) - agent-usage-chart.tsx: Bar fill hex → var(--chart-1) - team-usage-chart.tsx: Bar fill hex → var(--chart-1) - kanban-card.tsx: min-h-11 → max-sm:min-h-11 (44px touch target mobile-only, 3 buttons) - secretary-tab.tsx: already compliant (Button + design-system tokens), no change needed Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> * [d62036bd] Backend: Fix git endpoint schemas, add safety gates, and unit tests (CEO revision) (#200) * [d0593fe3] feat(git): remove agent_id from schemas and add service-layer safety gates (#195) - Remove agent_id field from all 9 git request schemas (GitCreateBranchRequest, GitCheckoutRequest, GitCommitRequest, GitPushRequest, GitCreatePRRequest, GitMergePRRequest, GitPullRequest, GitFetchRequest, GitRebaseRequest); agent identity comes from JWT auth context - Make task_id Optional[UUID]=None in GitPullRequest, GitFetchRequest, GitRebaseRequest - Add field_validator to GitRebaseRequest rejecting target_branch starting with '-' or equal to 'master'/'main' - Add lightweight PullRequest, FetchRequest, RebaseRequest schemas for gateway layer - Add dirty-workspace check to GitService.pull() (raises ValidationError if porcelain output) - Switch GitService.pull() to --ff-only; raises ValidationError with diverged-branch message on non-zero exit - Add master/main guard to GitService.rebase() for both head_branch and target_branch - Update callers: routes/tasks.py (2x), services/task.py, tests/unit/services/test_git.py (2x) Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> * [a2f96961] Add role-gated rebase endpoint and unit tests (test_git_rebase.py) (#198) * [a2f96961] feat(git): add role-gated rebase endpoint and unit tests Add role-gate to POST /rebase restricting access to DEVELOPER and CELL_PM roles; add master/main protected-branch guard to GitService.rebase() before any git subprocess runs; add 4 unit tests in tests/unit/services/test_git_rebase.py covering both target-branch and head-branch REBASE_FORBIDDEN cases * [a2f96961] fix(git): invert rebase role gate, add ownership check, schema validator, and missing tests - _REBASE_ALLOWED_ROLES changed from {DEVELOPER, CELL_PM} to {CEO, CELL_PM, MAIN_PM} so developers correctly receive 403 per AC1/AC2 - rebase_branch() now verifies task ownership for non-CEO PM callers: if task_id is provided and the task is not assigned to the calling agent, returns 403/404 - GitRebaseRequest.target_branch gets a @field_validator rejecting '-' prefix names and protected branch names (main, master, develop) - GitService.pull() gains pre-flight safety gates: raises ValidationError DIRTY_TREE when staged/unstaged changes exist, DIVERGED_BRANCH when ahead > 0 and behind > 0 - test_git_rebase.py adds 9 new tests: pull() dirty-tree ValidationError, pull() diverged-branch ValidationError, pull() success path, schema validator for '-' prefix and protected names, and route-level tests confirming HTTP 403 for DEVELOPER and HTTP 200 for CELL_PM on POST /rebase * [a2f96961] fix(tests): add type annotations for tuple variables in test_git_rebase.py mypy needs explicit tuple type annotations when assigning bare tuples to variables used as mock side_effect return values — fixes var-annotated error caught by the server-side quality gate --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Renn F <rennf93@users.noreply.github.com> --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Renn F <rennf93@users.noreply.github.com> * [94015c6d] Frontend R3: Fix legacy git taskId coercion + rebase placeholder + phantom fields (#204) * [401ddb40] fix(git): remove phantom fields from GitPullRequest/GitFetchRequest and make task_id optional in write request interfaces; use taskId || undefined in git-browser.tsx handlers to avoid 422 errors when no task context is active (#201) Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> * [cca8d0c0] fix(git): fix rebase placeholder and surface backend error in toast (#202) git-actions-panel.tsx: change rebase target_branch Input placeholder from "e.g. main or origin/main" to "Remote ref (e.g. origin/HEAD)" so no default branch name (main/master/develop) is suggested. git-browser.tsx: import getErrorMessage from @/lib/api/client and use it in handleRebase catch block instead of the hardcoded string "Failed to rebase". getErrorMessage extracts the real detail from AxiosError.response.data.detail and falls back to a non-empty generic message, satisfying both the detail-surfacing and fallback criteria. Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> * [1ea0fbcb] Backend R3: Relax legacy git schemas + fix integration tests (#206) * [219c539b] Make task_id Optional in git request schemas and update service methods (#205) * [219c539b] feat(git): make task_id Optional in git schemas and add None-guards in service methods - GitCommitRequest, GitPushRequest, GitCreatePRRequest, GitMergePRRequest now have task_id: UUID | None = None - commit_for_task, push_for_task, create_pr_for_task, merge_pr_for_task skip ownership/state checks when task_id is None and proceed to the git operation - Added 16 unit tests in tests/unit/api/routes/test_git_optional_task_id.py covering schema validation and HTTP endpoint responses - Added 4 integration tests in tests/integration/test_git_routes.py for no-422 behaviour - All quality gates pass: ruff format, ruff check, mypy, pytest * [219c539b] fix(tests): remove unused type-ignore comments, redundant cast, and invalid agent_id kwarg in git_optional_task_id unit tests --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> * [de95ce94] test(git): fix 3 rebase integration tests to use non-protected target_branch (#203) - Add pm_git_client fixture (CELL_PM role) needed for the role-gated rebase endpoint - Change target_branch from 'main' to 'develop' in test_rebase_success, test_rebase_conflict, and test_rebase_git_command_error - Remove task_id from request bodies (optional field; random UUIDs trigger 404) - Switch all 3 rebase tests to use pm_git_client instead of git_client Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> * chore: ruff format test_agent_image_registry.py (unblock quality gate) --------- Co-authored-by: Backend Developer 1 <be-dev-1@agents.roboco.dev> Co-authored-by: Backend Developer 2 <be-dev-2@agents.roboco.dev> Co-authored-by: Frontend Developer 1 <fe-dev-1@agents.roboco.dev> Co-authored-by: Frontend Developer 2 <fe-dev-2@agents.roboco.dev> Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
co-authored by
Backend Developer 2
Backend Developer 1
Frontend Developer 1
Frontend Developer 2
Renn F
parent
e71a746499
commit
818f2ac7a6
@@ -15,6 +15,17 @@ import {
|
||||
DialogFooter,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import {
|
||||
GitCommit,
|
||||
Upload,
|
||||
@@ -22,6 +33,9 @@ import {
|
||||
GitMerge,
|
||||
RefreshCw,
|
||||
ArrowUp,
|
||||
Download,
|
||||
RefreshCcw,
|
||||
GitGraph,
|
||||
} from "lucide-react";
|
||||
|
||||
interface GitActionsPanelProps {
|
||||
@@ -33,10 +47,16 @@ interface GitActionsPanelProps {
|
||||
onPush: (force?: boolean) => void;
|
||||
onCreatePR: (title: string, body: string) => void;
|
||||
onMergePR: (prNumber: number) => void;
|
||||
onPull: () => void;
|
||||
onFetch: () => void;
|
||||
onRebase: (targetBranch: string) => void;
|
||||
isCommitting: boolean;
|
||||
isPushing: boolean;
|
||||
isCreatingPR: boolean;
|
||||
isMerging: boolean;
|
||||
isPulling: boolean;
|
||||
isFetching: boolean;
|
||||
isRebasing: boolean;
|
||||
}
|
||||
|
||||
export function GitActionsPanel({
|
||||
@@ -48,10 +68,16 @@ export function GitActionsPanel({
|
||||
onPush,
|
||||
onCreatePR,
|
||||
onMergePR,
|
||||
onPull,
|
||||
onFetch,
|
||||
onRebase,
|
||||
isCommitting,
|
||||
isPushing,
|
||||
isCreatingPR,
|
||||
isMerging,
|
||||
isPulling,
|
||||
isFetching,
|
||||
isRebasing,
|
||||
}: GitActionsPanelProps) {
|
||||
void _agentId; // Reserved for future use
|
||||
const [showCommitDialog, setShowCommitDialog] = useState(false);
|
||||
@@ -61,6 +87,7 @@ export function GitActionsPanel({
|
||||
const [prTitle, setPrTitle] = useState("");
|
||||
const [prBody, setPrBody] = useState("");
|
||||
const [mergePrNumber, setMergePrNumber] = useState("");
|
||||
const [rebaseTargetBranch, setRebaseTargetBranch] = useState("");
|
||||
|
||||
const hasStagedChanges = (status?.staged_files.length ?? 0) > 0;
|
||||
const hasUnpushedCommits = (status?.ahead ?? 0) > 0;
|
||||
@@ -302,6 +329,88 @@ export function GitActionsPanel({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Pull Action */}
|
||||
<Button
|
||||
className="w-full justify-start"
|
||||
variant="outline"
|
||||
disabled={isPulling}
|
||||
onClick={onPull}
|
||||
>
|
||||
{isPulling ? (
|
||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Pull from Remote
|
||||
</Button>
|
||||
|
||||
{/* Fetch Action */}
|
||||
<Button
|
||||
className="w-full justify-start"
|
||||
variant="outline"
|
||||
disabled={isFetching}
|
||||
onClick={onFetch}
|
||||
>
|
||||
{isFetching ? (
|
||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<RefreshCcw className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Fetch Remote
|
||||
</Button>
|
||||
|
||||
{/* Rebase Action — destructive, requires confirmation */}
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
className="w-full justify-start"
|
||||
variant="outline"
|
||||
disabled={isRebasing}
|
||||
>
|
||||
{isRebasing ? (
|
||||
<RefreshCw className="h-4 w-4 mr-2 animate-spin" />
|
||||
) : (
|
||||
<GitGraph className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Rebase onto Remote
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="border-destructive bg-destructive/5">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Rebase onto target branch?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will rewrite the commit history of branch{" "}
|
||||
<strong>{status?.current_branch}</strong> by replaying commits
|
||||
on top of the specified target branch. A force-push will be
|
||||
required afterward. This action cannot be undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="px-6 py-2 space-y-2">
|
||||
<label className="text-sm font-medium">Target branch</label>
|
||||
<Input
|
||||
placeholder="Remote ref (e.g. origin/HEAD)"
|
||||
value={rebaseTargetBranch}
|
||||
onChange={(e) => setRebaseTargetBranch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={() => setRebaseTargetBranch("")}>
|
||||
Cancel
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
disabled={!rebaseTargetBranch.trim()}
|
||||
onClick={() => {
|
||||
onRebase(rebaseTargetBranch.trim());
|
||||
setRebaseTargetBranch("");
|
||||
}}
|
||||
>
|
||||
Rebase
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Status Summary */}
|
||||
{status && (
|
||||
<div className="pt-2 border-t text-xs text-muted-foreground space-y-1">
|
||||
|
||||
@@ -164,14 +164,15 @@ export function GitBranchPanel({
|
||||
</h4>
|
||||
<div className="space-y-0.5">
|
||||
{localBranches.map((branch) => (
|
||||
<button
|
||||
<Button
|
||||
key={branch.name}
|
||||
onClick={() => !branch.is_current && onCheckout(branch.name)}
|
||||
disabled={branch.is_current || isCheckingOut}
|
||||
variant="ghost"
|
||||
className={
|
||||
"w-full flex items-center justify-between px-2 py-1.5 rounded text-sm text-left transition-colors " +
|
||||
"w-full h-auto justify-between px-2 py-1.5 text-sm font-normal whitespace-normal " +
|
||||
(branch.is_current
|
||||
? "bg-primary/10 text-primary"
|
||||
? "bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary"
|
||||
: "hover:bg-muted")
|
||||
}
|
||||
>
|
||||
@@ -188,7 +189,7 @@ export function GitBranchPanel({
|
||||
{branch.last_commit.slice(0, 7)}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -202,16 +203,17 @@ export function GitBranchPanel({
|
||||
</h4>
|
||||
<div className="space-y-0.5">
|
||||
{remoteBranches.map((branch) => (
|
||||
<button
|
||||
<Button
|
||||
key={branch.name}
|
||||
onClick={() => onCheckout(branch.name)}
|
||||
disabled={isCheckingOut}
|
||||
className="w-full flex items-center justify-between px-2 py-1.5 rounded text-sm text-left transition-colors hover:bg-muted"
|
||||
variant="ghost"
|
||||
className="w-full h-auto justify-start px-2 py-1.5 text-sm font-normal whitespace-normal hover:bg-muted"
|
||||
>
|
||||
<span className="truncate font-mono text-xs text-muted-foreground">
|
||||
{branch.name}
|
||||
</span>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -29,6 +29,7 @@ import { GitDiffViewer } from "./git-diff-viewer";
|
||||
import { GitActionsPanel } from "./git-actions-panel";
|
||||
import { GitBranch, RefreshCw, FolderGit2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import { getErrorMessage } from "@/lib/api/client";
|
||||
|
||||
function GitBrowserContent() {
|
||||
const router = useRouter();
|
||||
@@ -49,7 +50,7 @@ function GitBrowserContent() {
|
||||
const { data: unstagedDiff, isLoading: loadingUnstagedDiff } = useGitDiff(projectSlug, false, undefined, !!projectSlug);
|
||||
|
||||
// Git operations
|
||||
const { commit, push, createBranch, checkout, createPR, mergePR } = useGitOperations();
|
||||
const { commit, push, createBranch, checkout, createPR, mergePR, pull, fetch, rebase } = useGitOperations();
|
||||
|
||||
// Update URL params
|
||||
const updateParams = useCallback(
|
||||
@@ -115,7 +116,7 @@ function GitBrowserContent() {
|
||||
const result = await commit.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
message,
|
||||
task_id: taskId || "manual",
|
||||
task_id: taskId || undefined,
|
||||
agent_id: "ceo",
|
||||
});
|
||||
toast.success(`Committed: ${result.commit_hash.slice(0, 7)}`);
|
||||
@@ -128,7 +129,7 @@ function GitBrowserContent() {
|
||||
try {
|
||||
const result = await push.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
task_id: taskId || "manual",
|
||||
task_id: taskId || undefined,
|
||||
agent_id: "ceo",
|
||||
force,
|
||||
});
|
||||
@@ -142,7 +143,7 @@ function GitBrowserContent() {
|
||||
try {
|
||||
const result = await createPR.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
task_id: taskId || "manual",
|
||||
task_id: taskId || undefined,
|
||||
title,
|
||||
body,
|
||||
agent_id: "ceo",
|
||||
@@ -165,7 +166,7 @@ function GitBrowserContent() {
|
||||
const result = await mergePR.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
pr_number: prNumber,
|
||||
task_id: taskId || "manual",
|
||||
task_id: taskId || undefined,
|
||||
agent_id: "ceo",
|
||||
});
|
||||
toast.success(`Merged PR #${result.pr_number} → ${result.target_branch}`);
|
||||
@@ -174,6 +175,50 @@ function GitBrowserContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePull = async () => {
|
||||
try {
|
||||
const result = await pull.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
task_id: taskId || undefined,
|
||||
});
|
||||
toast.success(`Pulled: now on ${result.current_branch}`);
|
||||
} catch {
|
||||
toast.error("Failed to pull from remote");
|
||||
}
|
||||
};
|
||||
|
||||
const handleFetch = async () => {
|
||||
try {
|
||||
const result = await fetch.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
task_id: taskId || undefined,
|
||||
});
|
||||
toast.success(`Fetched: now on ${result.current_branch}`);
|
||||
} catch {
|
||||
toast.error("Failed to fetch from remote");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRebase = async (targetBranch: string) => {
|
||||
try {
|
||||
const result = await rebase.mutateAsync({
|
||||
project_slug: projectSlug,
|
||||
target_branch: targetBranch,
|
||||
task_id: taskId || undefined,
|
||||
agent_id: "ceo",
|
||||
});
|
||||
if (result.conflict) {
|
||||
toast.warning(
|
||||
`Rebase conflicts in: ${result.conflicted_files.join(", ") || "unknown files"}`
|
||||
);
|
||||
} else {
|
||||
toast.success("Rebase completed successfully");
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(getErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
// Check offline
|
||||
const isOffline = projectsError && (
|
||||
projectsError.message?.includes("Network Error") ||
|
||||
@@ -258,10 +303,16 @@ function GitBrowserContent() {
|
||||
onPush={handlePush}
|
||||
onCreatePR={handleCreatePR}
|
||||
onMergePR={handleMergePR}
|
||||
onPull={handlePull}
|
||||
onFetch={handleFetch}
|
||||
onRebase={handleRebase}
|
||||
isCommitting={commit.isPending}
|
||||
isPushing={push.isPending}
|
||||
isCreatingPR={createPR.isPending}
|
||||
isMerging={mergePR.isPending}
|
||||
isPulling={pull.isPending}
|
||||
isFetching={fetch.isPending}
|
||||
isRebasing={rebase.isPending}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import { GitLogResponse, CommitInfo } from "@/types/git";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { GitCommit, User, Calendar } from "lucide-react";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
|
||||
@@ -68,15 +70,14 @@ export function GitLogPanel({
|
||||
<ScrollArea className="h-80">
|
||||
<div className="p-4 space-y-0">
|
||||
{log.commits.map((commit, index) => (
|
||||
<button
|
||||
<Button
|
||||
key={commit.hash}
|
||||
onClick={() => onSelectCommit?.(commit)}
|
||||
className={
|
||||
"w-full text-left p-3 rounded-lg transition-colors relative " +
|
||||
(selectedHash === commit.hash
|
||||
? "bg-primary/10"
|
||||
: "hover:bg-muted")
|
||||
}
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
"w-full h-auto justify-start text-left p-3 font-normal whitespace-normal relative",
|
||||
selectedHash === commit.hash ? "bg-primary/10 hover:bg-primary/10" : ""
|
||||
)}
|
||||
>
|
||||
{/* Timeline line */}
|
||||
{index < log.commits.length - 1 && (
|
||||
@@ -123,7 +124,7 @@ export function GitLogPanel({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
Reference in New Issue
Block a user