From ec55c785122fdca15f6446bd888e449fb4709389 Mon Sep 17 00:00:00 2001 From: marcelatjamie Date: Tue, 6 Jan 2026 19:44:08 +0200 Subject: [PATCH 1/4] group by branch --- stream/.gitignore | 2 - stream/.vscode/tasks.json | 28 ++ stream/src/components/commit-overlay.tsx | 368 +++++++++++++++-------- stream/src/ipc/git-reader.ts | 13 + 4 files changed, 278 insertions(+), 133 deletions(-) create mode 100644 stream/.vscode/tasks.json diff --git a/stream/.gitignore b/stream/.gitignore index 0ac9580..f3c4288 100644 --- a/stream/.gitignore +++ b/stream/.gitignore @@ -13,8 +13,6 @@ dist-ssr *.local # Editor directories and files -.vscode/* -!.vscode/extensions.json .idea .DS_Store *.suo diff --git a/stream/.vscode/tasks.json b/stream/.vscode/tasks.json new file mode 100644 index 0000000..0ea6543 --- /dev/null +++ b/stream/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "check:fix", + "type": "shell", + "command": "pnpm check:fix", + "group": "build", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "shared" + } + }, + { + "label": "dev", + "type": "shell", + "command": "pnpm tauri dev", + "group": "build", + "isBackground": true, + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "dedicated" + } + } + ] +} diff --git a/stream/src/components/commit-overlay.tsx b/stream/src/components/commit-overlay.tsx index 288e459..453a3cb 100644 --- a/stream/src/components/commit-overlay.tsx +++ b/stream/src/components/commit-overlay.tsx @@ -1,6 +1,11 @@ "use client"; -import { ArrowSquareOutIcon, GitBranchIcon } from "@phosphor-icons/react"; +import { + ArrowSquareOutIcon, + GitBranchIcon, + MinusIcon, + PlusIcon, +} from "@phosphor-icons/react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { useState } from "react"; import { @@ -10,10 +15,15 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import type { GitCommit } from "@/ipc/git-reader"; -import { formatCommitAuthor, getShortCommitId } from "@/ipc/git-reader"; +import { + formatCommitAuthor, + getShortCommitId, + isMainBranch, +} from "@/ipc/git-reader"; interface CommitOverlayProps { commits: GitCommit[]; @@ -26,8 +36,49 @@ interface RepoCardProps { commits: GitCommit[]; } -function RepoCard({ repoName, commits }: RepoCardProps) { - const [expanded, setExpanded] = useState(undefined); +interface BranchGroupProps { + branchName: string; + commits: GitCommit[]; +} + +function groupCommitsByBranch( + commits: GitCommit[], +): Record { + const byBranch: Record = {}; + + for (const commit of commits) { + for (const branch of commit.branches) { + const cleanBranch = branch.replace("origin/", ""); + if (!byBranch[cleanBranch]) { + byBranch[cleanBranch] = []; + } + if (!byBranch[cleanBranch].some((c) => c.id === commit.id)) { + byBranch[cleanBranch].push(commit); + } + } + } + + return byBranch; +} + +function sortBranchNames(branches: string[]): string[] { + return branches.sort((a, b) => { + const aIsMain = isMainBranch(a); + const bIsMain = isMainBranch(b); + if (aIsMain && !bIsMain) return -1; + if (!aIsMain && bIsMain) return 1; + return a.localeCompare(b); + }); +} + +function truncateFilePath(filePath: string): string { + const parts = filePath.split("/"); + if (parts.length <= 2) return filePath; + return `.../${parts.slice(-2).join("/")}`; +} + +function BranchGroup({ branchName, commits }: BranchGroupProps) { + const [expanded, setExpanded] = useState(false); const [expandedFiles, setExpandedFiles] = useState>(new Set()); const toggleFileExpansion = (commitId: string) => { @@ -42,6 +93,176 @@ function RepoCard({ repoName, commits }: RepoCardProps) { }); }; + const sortedCommits = [...commits].sort((a, b) => b.timestamp - a.timestamp); + const firstCommit = sortedCommits[0]; + const remainingCommits = sortedCommits.slice(1); + + return ( +
+ + +
+ +
+ + {expanded && remainingCommits.length > 0 && ( +
+ {remainingCommits.map((commit) => ( +
+ +
+ ))} +
+ )} +
+ ); +} + +interface CommitItemProps { + commit: GitCommit; + expandedFiles: Set; + toggleFileExpansion: (commitId: string) => void; + compact?: boolean; +} + +function CommitItem({ + commit, + expandedFiles, + toggleFileExpansion, + compact = false, +}: CommitItemProps) { + const time = new Date(commit.timestamp).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); + const url = commit.url; + + return ( +
+
+
+ {time} + + + {commit.message} + + {url ? ( + + ) : ( + + {getShortCommitId(commit.id)} + + )} +
+ + {formatCommitAuthor(commit)} + +
+ + {!compact && commit.files_changed.length > 0 && ( +
+
+ {(() => { + const isExpanded = expandedFiles.has(commit.id); + const filesToShow = isExpanded + ? commit.files_changed + : commit.files_changed.slice(0, 3); + const remainingCount = commit.files_changed.length - 3; + + return ( + <> + {filesToShow.map((file) => ( + + {truncateFilePath(file)} + + ))} + {!isExpanded && remainingCount > 0 && ( + { + e.stopPropagation(); + toggleFileExpansion(commit.id); + }} + > + +{remainingCount} more + + )} + {isExpanded && commit.files_changed.length > 3 && ( + { + e.stopPropagation(); + toggleFileExpansion(commit.id); + }} + > + less + + )} + + ); + })()} +
+
+ )} +
+ ); +} + +function RepoCard({ repoName, commits }: RepoCardProps) { + const [expanded, setExpanded] = useState(undefined); + + const commitsByBranch = groupCommitsByBranch(commits); + const sortedBranches = sortBranchNames(Object.keys(commitsByBranch)); + return ( - -
- {commits.map((commit) => { - const time = new Date(commit.timestamp).toLocaleTimeString(); - const url = commit.url; - return ( -
-
-
- {url ? ( - - ) : ( - - {getShortCommitId(commit.id)} - - )} - - {time} - -
- {commit.branches.map((branch) => { - const cleanBranch = branch.replace("origin/", ""); - const isMainBranch = [ - "main", - "master", - "develop", - ].includes(cleanBranch); - return ( - - {cleanBranch} - - ); - })} -
-
- - {formatCommitAuthor(commit)} - -
-
- {commit.message} -
- {commit.files_changed.length > 0 && ( -
-
- Files -
- -
- {(() => { - const isExpanded = expandedFiles.has(commit.id); - const filesToShow = isExpanded - ? commit.files_changed - : commit.files_changed.slice(0, 3); - const remainingCount = - commit.files_changed.length - 3; - - return ( - <> - {filesToShow.map((file) => ( - - {file} - - ))} - {!isExpanded && remainingCount > 0 && ( - - toggleFileExpansion(commit.id) - } - > - +{remainingCount} others - - )} - {isExpanded && - commit.files_changed.length > 3 && ( - - toggleFileExpansion(commit.id) - } - > - Show less - - )} - - ); - })()} -
-
-
- )} -
- ); - })} -
+ + +
+ {sortedBranches.map((branchName) => ( + + ))} +
+
@@ -205,7 +312,6 @@ export function CommitOverlay({ commits, className = "" }: CommitOverlayProps) { return null; } - // Group commits by repository const commitsByRepo = commits.reduce( (acc, commit) => { const repoName = commit.repo_path.split("/").pop() || commit.repo_path; diff --git a/stream/src/ipc/git-reader.ts b/stream/src/ipc/git-reader.ts index fa8dbc8..7741847 100644 --- a/stream/src/ipc/git-reader.ts +++ b/stream/src/ipc/git-reader.ts @@ -185,6 +185,19 @@ export function getShortCommitId(commitId: string): string { return commitId.substring(0, 7); } +const MAIN_BRANCH_NAMES = [ + "main", + "master", + "origin/main", + "origin/master", + "develop", + "origin/develop", +]; + +export function isMainBranch(branchName: string): boolean { + return MAIN_BRANCH_NAMES.includes(branchName); +} + /** * Filter commits based on author, repository, and search criteria */ From 0ad06ffe9bba3ee359bd42018a9b9c0c2b3759bf Mon Sep 17 00:00:00 2001 From: marcelatjamie Date: Tue, 6 Jan 2026 19:47:34 +0200 Subject: [PATCH 2/4] clean up --- stream/src/components/commit-overlay.tsx | 106 +++++++++++++---------- stream/src/components/ui/accordion.tsx | 6 +- 2 files changed, 61 insertions(+), 51 deletions(-) diff --git a/stream/src/components/commit-overlay.tsx b/stream/src/components/commit-overlay.tsx index 453a3cb..488af52 100644 --- a/stream/src/components/commit-overlay.tsx +++ b/stream/src/components/commit-overlay.tsx @@ -2,9 +2,8 @@ import { ArrowSquareOutIcon, + CaretDownIcon, GitBranchIcon, - MinusIcon, - PlusIcon, } from "@phosphor-icons/react"; import { openUrl } from "@tauri-apps/plugin-opener"; import { useState } from "react"; @@ -15,7 +14,6 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { ScrollArea } from "@/components/ui/scroll-area"; import type { GitCommit } from "@/ipc/git-reader"; @@ -78,7 +76,7 @@ function truncateFilePath(filePath: string): string { } function BranchGroup({ branchName, commits }: BranchGroupProps) { - const [expanded, setExpanded] = useState(false); + const [expanded, setExpanded] = useState(undefined); const [expandedFiles, setExpandedFiles] = useState>(new Set()); const toggleFileExpansion = (commitId: string) => { @@ -96,55 +94,67 @@ function BranchGroup({ branchName, commits }: BranchGroupProps) { const sortedCommits = [...commits].sort((a, b) => b.timestamp - a.timestamp); const firstCommit = sortedCommits[0]; const remainingCommits = sortedCommits.slice(1); + const isExpanded = expanded === "branch"; return ( -
- - -
- -
- - {expanded && remainingCommits.length > 0 && ( -
- {remainingCommits.map((commit) => ( -
- + + +
+ {branchName} + + {commits.length} + +
+ {remainingCommits.length > 0 && ( +
+ + {isExpanded ? "less" : `${remainingCommits.length} more`} + +
- ))} + )} +
+ +
+
- )} -
+ + {remainingCommits.length > 0 && ( + +
+ {remainingCommits.map((commit) => ( +
+ +
+ ))} +
+
+ )} + + ); } diff --git a/stream/src/components/ui/accordion.tsx b/stream/src/components/ui/accordion.tsx index ec4490f..d73cc48 100644 --- a/stream/src/components/ui/accordion.tsx +++ b/stream/src/components/ui/accordion.tsx @@ -35,13 +35,13 @@ function AccordionTrigger({ svg]:rotate-180", + "flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left font-medium text-sm outline-none hover:underline focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className, )} {...props} > {children} - + ); @@ -55,7 +55,7 @@ function AccordionContent({ return (
{children}
From e251fa79d7ecd5e60f8a6606747ab516d335cabb Mon Sep 17 00:00:00 2001 From: marcelatjamie Date: Tue, 6 Jan 2026 20:04:14 +0200 Subject: [PATCH 3/4] clean --- stream/src/components/commit-overlay.tsx | 104 +++++++++++------------ 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/stream/src/components/commit-overlay.tsx b/stream/src/components/commit-overlay.tsx index 488af52..952c976 100644 --- a/stream/src/components/commit-overlay.tsx +++ b/stream/src/components/commit-overlay.tsx @@ -3,6 +3,7 @@ import { ArrowSquareOutIcon, CaretDownIcon, + DotOutlineIcon, GitBranchIcon, } from "@phosphor-icons/react"; import { openUrl } from "@tauri-apps/plugin-opener"; @@ -96,6 +97,8 @@ function BranchGroup({ branchName, commits }: BranchGroupProps) { const remainingCommits = sortedCommits.slice(1); const isExpanded = expanded === "branch"; + const allCommitsForTimeline = isExpanded ? sortedCommits : [firstCommit]; + return ( - +
{branchName} - - {commits.length} -
{remainingCommits.length > 0 && (
@@ -121,38 +118,27 @@ function BranchGroup({ branchName, commits }: BranchGroupProps) { {isExpanded ? "less" : `${remainingCommits.length} more`}
)}
-
- +
+ {allCommitsForTimeline.map((commit, index) => ( + + ))}
- {remainingCommits.length > 0 && ( - -
- {remainingCommits.map((commit) => ( -
- -
- ))} -
-
- )} + {remainingCommits.length > 0 && } ); @@ -163,6 +149,7 @@ interface CommitItemProps { expandedFiles: Set; toggleFileExpansion: (commitId: string) => void; compact?: boolean; + isLast?: boolean; } function CommitItem({ @@ -170,6 +157,7 @@ function CommitItem({ expandedFiles, toggleFileExpansion, compact = false, + isLast = false, }: CommitItemProps) { const time = new Date(commit.timestamp).toLocaleTimeString([], { hour: "2-digit", @@ -178,16 +166,32 @@ function CommitItem({ const url = commit.url; return ( -
-
-
- {time} +
+ {/* Timeline dot */} +
+ {/* Timeline line */} + {!isLast && ( +
+ )} +
+ {/* Row 1: Time + Message */} +
+ {time} {commit.message} +
+ + {/* Row 2: Author + Commit ID */} +
+ {formatCommitAuthor(commit)} + {url ? ( ) : ( - - {getShortCommitId(commit.id)} - + {getShortCommitId(commit.id)} )}
- - {formatCommitAuthor(commit)} - -
- {!compact && commit.files_changed.length > 0 && ( -
-
+ {/* Row 3: File badges */} + {!compact && commit.files_changed.length > 0 && ( +
{(() => { const isExpanded = expandedFiles.has(commit.id); const filesToShow = isExpanded @@ -228,7 +226,7 @@ function CommitItem({ {truncateFilePath(file)} @@ -236,7 +234,7 @@ function CommitItem({ {!isExpanded && remainingCount > 0 && ( { e.stopPropagation(); toggleFileExpansion(commit.id); @@ -248,7 +246,7 @@ function CommitItem({ {isExpanded && commit.files_changed.length > 3 && ( { e.stopPropagation(); toggleFileExpansion(commit.id); @@ -261,8 +259,8 @@ function CommitItem({ ); })()}
-
- )} + )} +
); } From 1b547516d81125fd38a389a513da286248aaef4f Mon Sep 17 00:00:00 2001 From: marcelatjamie Date: Tue, 6 Jan 2026 20:04:35 +0200 Subject: [PATCH 4/4] version bump --- stream/src-tauri/tauri.conf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stream/src-tauri/tauri.conf.json b/stream/src-tauri/tauri.conf.json index bbb7987..b65d6aa 100644 --- a/stream/src-tauri/tauri.conf.json +++ b/stream/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "stream", - "version": "0.2.5", + "version": "0.2.6", "identifier": "com.marcelmarais.stream", "build": { "beforeDevCommand": "pnpm dev",