From 6ec51794084ec0086e3b7394f179d07ce121315d Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 3 Jun 2026 08:51:34 +0000 Subject: [PATCH] chore: add dev branch policy to AGENTS.md - Document that dev is the default working branch - main is reserved for stable/production merges only --- AGENTS.md | 7 + .../components/features/git/FileEditor.tsx | 533 +++++++++--------- apps/web/src/components/ui/Icon.tsx | 302 +++++----- apps/web/src/pages/RepoWorkspacePage.tsx | 6 +- 4 files changed, 434 insertions(+), 414 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3d0a94b..d64e072 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -85,6 +85,13 @@ Before completion, report: Do not claim completion without verification evidence. +## Git branch policy + +- **Default working branch:** `dev` — all commits and pushes target `dev` unless the user explicitly requests otherwise. +- `main` is the stable/production branch; merge to `main` only when explicitly instructed. +- After committing, push to `origin/dev`. +- If `dev` does not exist locally, create it from `main` or fetch it from origin. + ## Git workflow ### Auto-commit on spec completion diff --git a/apps/web/src/components/features/git/FileEditor.tsx b/apps/web/src/components/features/git/FileEditor.tsx index e458e70..f2e85da 100644 --- a/apps/web/src/components/features/git/FileEditor.tsx +++ b/apps/web/src/components/features/git/FileEditor.tsx @@ -10,296 +10,299 @@ import { SyntaxHighlighter } from "./SyntaxHighlighter"; import { detectLanguage } from "../../../utils/language"; interface GitFileStatus { - modified: string[]; - added: string[]; - deleted: string[]; - untracked: string[]; + modified: string[]; + added: string[]; + deleted: string[]; + untracked: string[]; } interface FileEditorProps { - projectId: string; - repoId: string; - gitStatus?: GitFileStatus | null; + projectId: string; + repoId: string; + gitStatus?: GitFileStatus | null; } export const FileEditor: React.FC = ({ - projectId, - repoId, - gitStatus, + projectId, + repoId, + gitStatus, }) => { - const [searchParams] = useSearchParams(); - const { user } = useAuth(); + const [searchParams] = useSearchParams(); + const { user } = useAuth(); - const [mode, setMode] = useState<"view" | "edit">("view"); - const [content, setContent] = useState(">"); - const [originalContent, setOriginalContent] = useState(">"); - const [language, setLanguage] = useState("plaintext"); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [showCommitDialog, setShowCommitDialog] = useState(false); - const [isBinary, setIsBinary] = useState(false); - const [saving, setSaving] = useState(false); + const [mode, setMode] = useState<"view" | "edit">("view"); + const [content, setContent] = useState(">"); + const [originalContent, setOriginalContent] = useState(">"); + const [language, setLanguage] = useState("plaintext"); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [showCommitDialog, setShowCommitDialog] = useState(false); + const [isBinary, setIsBinary] = useState(false); + const [saving, setSaving] = useState(false); - const handleDiscard = async () => { - if (!filePath) return; - try { - const response = await apiClient.get( - `/projects/${projectId}/repositories/${repoId}/files/content`, - { - params: { - branch, - path: filePath, - }, - } - ); - const data = response.data; - if (data.is_binary) { - setIsBinary(true); - setContent("Binary file - cannot display"); - setOriginalContent(""); - } else { - setIsBinary(false); - setContent(data.content); - setOriginalContent(data.content); - } - setMode("view"); - } catch { - setError("Failed to discard changes"); - } - }; + const handleDiscard = async () => { + if (!filePath) return; + try { + const response = await apiClient.get( + `/projects/${projectId}/repositories/${repoId}/files/content`, + { + params: { + branch, + path: filePath, + }, + }, + ); + const data = response.data; + if (data.is_binary) { + setIsBinary(true); + setContent("Binary file - cannot display"); + setOriginalContent(""); + } else { + setIsBinary(false); + setContent(data.content); + setOriginalContent(data.content); + } + setMode("view"); + } catch { + setError("Failed to discard changes"); + } + }; - const branch = searchParams.get("branch") || "main"; - const filePath = searchParams.get("file"); + const branch = searchParams.get("branch") || "main"; + const filePath = searchParams.get("file"); - const fileStatus = gitStatus - ? gitStatus.modified.includes(filePath || "") - ? "modified" - : gitStatus.added.includes(filePath || "") - ? "added" - : gitStatus.deleted.includes(filePath || "") - ? "deleted" - : gitStatus.untracked.includes(filePath || "") - ? "untracked" - : undefined - : undefined; + const fileStatus = gitStatus + ? gitStatus.modified.includes(filePath || "") + ? "modified" + : gitStatus.added.includes(filePath || "") + ? "added" + : gitStatus.deleted.includes(filePath || "") + ? "deleted" + : gitStatus.untracked.includes(filePath || "") + ? "untracked" + : undefined + : undefined; - const loadFile = useCallback(async () => { - if (!filePath) { - setContent(""); - setOriginalContent(""); - return; - } + const loadFile = useCallback(async () => { + if (!filePath) { + setContent(""); + setOriginalContent(""); + return; + } - setLoading(true); - setError(null); - try { - const response = await apiClient.get( - `/projects/${projectId}/repositories/${repoId}/files/content`, - { - params: { - branch, - path: filePath, - }, - } - ); - const data = response.data; - if (data.is_binary) { - setIsBinary(true); - setContent("Binary file - cannot display"); - setOriginalContent(""); - } else { - setIsBinary(false); - setContent(data.content); - setOriginalContent(data.content); - setLanguage(detectLanguage(filePath)); - } - } catch { - setError("Failed to load file"); - } finally { - setLoading(false); - } - }, [projectId, repoId, branch, filePath]); + setLoading(true); + setError(null); + try { + const response = await apiClient.get( + `/projects/${projectId}/repositories/${repoId}/files/content`, + { + params: { + branch, + path: filePath, + }, + }, + ); + const data = response.data; + if (data.is_binary) { + setIsBinary(true); + setContent("Binary file - cannot display"); + setOriginalContent(""); + } else { + setIsBinary(false); + setContent(data.content); + setOriginalContent(data.content); + setLanguage(detectLanguage(filePath)); + } + } catch { + setError("Failed to load file"); + } finally { + setLoading(false); + } + }, [projectId, repoId, branch, filePath]); - useEffect(() => { - void loadFile(); - }, [loadFile]); + useEffect(() => { + void loadFile(); + }, [loadFile]); - const handleEdit = () => { - if (isBinary) return; - setMode("edit"); - }; + const handleEdit = () => { + if (isBinary) return; + setMode("edit"); + }; - const handleCancel = () => { - setContent(originalContent); - setMode("view"); - setShowCommitDialog(false); - }; + const handleCancel = () => { + setContent(originalContent); + setMode("view"); + setShowCommitDialog(false); + }; - const handleSave = () => { - if (content === originalContent) { - setMode("view"); - return; - } - setShowCommitDialog(true); - }; + const handleSave = () => { + if (content === originalContent) { + setMode("view"); + return; + } + setShowCommitDialog(true); + }; - const handleCommit = async (message: string) => { - if (!filePath || !user) return; + const handleCommit = async (message: string) => { + if (!filePath || !user) return; - setSaving(true); - try { - await apiClient.post( - `/projects/${projectId}/repositories/${repoId}/files/content`, - { - path: filePath, - branch, - content, - commit_message: message, - author_name: user.name || "User", - author_email: user.email || "user@example.com", - } - ); - setOriginalContent(content); - setMode("view"); - setShowCommitDialog(false); - } catch { - setError("Failed to save changes"); - } finally { - setSaving(false); - } - }; + setSaving(true); + try { + await apiClient.post( + `/projects/${projectId}/repositories/${repoId}/files/content`, + { + path: filePath, + branch, + content, + commit_message: message, + author_name: user.name || "User", + author_email: user.email || "user@example.com", + }, + ); + setOriginalContent(content); + setMode("view"); + setShowCommitDialog(false); + } catch { + setError("Failed to save changes"); + } finally { + setSaving(false); + } + }; - // Keyboard shortcuts - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if ((e.ctrlKey || e.metaKey) && e.key === "e") { - e.preventDefault(); - if (mode === "view" && !isBinary) { - handleEdit(); - } else if (mode === "edit") { - handleCancel(); - } - } - if ((e.ctrlKey || e.metaKey) && e.key === "s") { - e.preventDefault(); - if (mode === "edit") { - handleSave(); - } - } - }; + // Keyboard shortcuts + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === "e") { + e.preventDefault(); + if (mode === "view" && !isBinary) { + handleEdit(); + } else if (mode === "edit") { + handleCancel(); + } + } + if ((e.ctrlKey || e.metaKey) && e.key === "s") { + e.preventDefault(); + if (mode === "edit") { + handleSave(); + } + } + }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [mode, isBinary, content, originalContent]); + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [mode, isBinary, content, originalContent]); - if (!filePath) { - return ( -
-

Select a file to view its contents

-
- ); - } + if (!filePath) { + return ( +
+

Select a file to view its contents

+
+ ); + } - if (loading) return

Loading file...

; - if (error) return

{error}

; + if (loading) return

Loading file...

; + if (error) return

{error}

; - return ( -
-
-
- {filePath?.split("/").map((part, i, arr) => ( - - {part} - {i < arr.length - 1 && ( - / - )} - - ))} -
-
- {fileStatus && ( - - {fileStatus === "modified" ? "M" : fileStatus === "added" ? "A" : fileStatus === "deleted" ? "D" : "?"} - - )} - {mode === "view" && !isBinary && ( - - )} - {mode === "edit" && ( - <> - - - - - )} -
-
+ return ( +
+
+
+ {filePath?.split("/").map((part, i, arr) => ( + + {part} + {i < arr.length - 1 && /} + + ))} +
+
+ {fileStatus && ( + + {fileStatus === "modified" + ? "M" + : fileStatus === "added" + ? "A" + : fileStatus === "deleted" + ? "D" + : "?"} + + )} + {mode === "view" && !isBinary && ( + + )} + {mode === "edit" && ( + <> + + + + + )} +
+
-
- {mode === "view" && ( - - )} +
+ {mode === "view" && ( + + )} - {mode === "edit" && ( - - )} -
+ {mode === "edit" && ( + + )} +
- setShowCommitDialog(false)} - /> -
- ); + setShowCommitDialog(false)} + /> +
+ ); }; diff --git a/apps/web/src/components/ui/Icon.tsx b/apps/web/src/components/ui/Icon.tsx index b870e3d..42fd71d 100644 --- a/apps/web/src/components/ui/Icon.tsx +++ b/apps/web/src/components/ui/Icon.tsx @@ -1,167 +1,173 @@ import React from "react"; import { - House, - Folder, - GitBranch, - Gear, - User, - SignOut, - Plus, - PencilSimple, - Trash, - FloppyDisk, - X, - ArrowsClockwise, - Copy, - MagnifyingGlass, - List, - Check, - Warning, - Info, - Spinner, - GitCommit, - GitMerge, - ClockCounterClockwise, - ArrowDown, - ArrowUp, - File, - FileText, - Image, - Binary, - Code, - ArrowSquareOut, - Play, - Stop, - Terminal, - ArrowLeft, + House, + Folder, + GitBranch, + Gear, + User, + SignOut, + Plus, + PencilSimple, + Trash, + FloppyDisk, + X, + ArrowsClockwise, + Copy, + MagnifyingGlass, + List, + Check, + Warning, + Info, + Spinner, + GitCommit, + GitMerge, + ClockCounterClockwise, + ArrowDown, + ArrowUp, + File, + FileText, + Image, + Binary, + Code, + ArrowSquareOut, + Play, + Stop, + Terminal, + ArrowLeft, } from "@phosphor-icons/react"; export type IconName = - | "dashboard" - | "projects" - | "repositories" - | "settings" - | "profile" - | "logout" - | "add" - | "edit" - | "delete" - | "save" - | "cancel" - | "refresh" - | "copy" - | "search" - | "menu" - | "close" - | "success" - | "error" - | "warning" - | "info" - | "loading" - | "branch" - | "commit" - | "merge" - | "history" - | "pull" - | "push" - | "fetch" - | "file" - | "folder" - | "code" - | "document" - | "image" - | "binary" - | "external" - | "play" - | "stop" - | "terminal" - | "arrow-left" - | "undo"; + | "dashboard" + | "projects" + | "repositories" + | "settings" + | "profile" + | "logout" + | "add" + | "edit" + | "delete" + | "save" + | "cancel" + | "refresh" + | "copy" + | "search" + | "menu" + | "close" + | "success" + | "error" + | "warning" + | "info" + | "loading" + | "branch" + | "commit" + | "merge" + | "history" + | "pull" + | "push" + | "fetch" + | "file" + | "folder" + | "code" + | "document" + | "image" + | "binary" + | "external" + | "play" + | "stop" + | "terminal" + | "arrow-left" + | "undo"; -const iconMap: Record> = { - dashboard: House, - projects: Folder, - repositories: GitBranch, - settings: Gear, - profile: User, - logout: SignOut, - add: Plus, - edit: PencilSimple, - delete: Trash, - save: FloppyDisk, - cancel: X, - refresh: ArrowsClockwise, - copy: Copy, - search: MagnifyingGlass, - menu: List, - close: X, - success: Check, - error: X, - warning: Warning, - info: Info, - loading: Spinner, - branch: GitBranch, - commit: GitCommit, - merge: GitMerge, - history: ClockCounterClockwise, - pull: ArrowDown, - push: ArrowUp, - fetch: ArrowsClockwise, - file: File, - folder: Folder, - code: Code, - document: FileText, - image: Image, - binary: Binary, - external: ArrowSquareOut, - play: Play, - stop: Stop, - terminal: Terminal, - "arrow-left": ArrowLeft, - undo: ClockCounterClockwise, +const iconMap: Record< + IconName, + React.ComponentType<{ + size?: number | string; + weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone"; + }> +> = { + dashboard: House, + projects: Folder, + repositories: GitBranch, + settings: Gear, + profile: User, + logout: SignOut, + add: Plus, + edit: PencilSimple, + delete: Trash, + save: FloppyDisk, + cancel: X, + refresh: ArrowsClockwise, + copy: Copy, + search: MagnifyingGlass, + menu: List, + close: X, + success: Check, + error: X, + warning: Warning, + info: Info, + loading: Spinner, + branch: GitBranch, + commit: GitCommit, + merge: GitMerge, + history: ClockCounterClockwise, + pull: ArrowDown, + push: ArrowUp, + fetch: ArrowsClockwise, + file: File, + folder: Folder, + code: Code, + document: FileText, + image: Image, + binary: Binary, + external: ArrowSquareOut, + play: Play, + stop: Stop, + terminal: Terminal, + "arrow-left": ArrowLeft, + undo: ClockCounterClockwise, }; export interface IconProps { - name: IconName; - size?: "sm" | "md" | "lg" | "xl"; - color?: string; - weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone"; - className?: string; - ariaLabel?: string; + name: IconName; + size?: "sm" | "md" | "lg" | "xl"; + color?: string; + weight?: "thin" | "light" | "regular" | "bold" | "fill" | "duotone"; + className?: string; + ariaLabel?: string; } const sizeMap: Record, number> = { - sm: 16, - md: 20, - lg: 24, - xl: 32, + sm: 16, + md: 20, + lg: 24, + xl: 32, }; export const Icon: React.FC = ({ - name, - size = "md", - color, - weight = "regular", - className, - ariaLabel, + name, + size = "md", + color, + weight = "regular", + className, + ariaLabel, }) => { - const IconComponent = iconMap[name]; - const sizeValue = sizeMap[size]; + const IconComponent = iconMap[name]; + const sizeValue = sizeMap[size]; - if (!IconComponent) { - console.warn(`Icon "${name}" not found`); - return null; - } + if (!IconComponent) { + console.warn(`Icon "${name}" not found`); + return null; + } - return ( - - - - ); + return ( + + + + ); }; diff --git a/apps/web/src/pages/RepoWorkspacePage.tsx b/apps/web/src/pages/RepoWorkspacePage.tsx index 62fd1b6..9b8cb63 100644 --- a/apps/web/src/pages/RepoWorkspacePage.tsx +++ b/apps/web/src/pages/RepoWorkspacePage.tsx @@ -198,7 +198,11 @@ export const RepoWorkspace = () => { )}
{selectedRepoId && ( - + )}