Skip to content
This repository was archived by the owner on Feb 17, 2026. It is now read-only.

Commit 42b852b

Browse files
committed
feat(spec-view): group version history by commit SHA
Previous version history dropdown showed all versions in a flat list, making it difficult to distinguish versions created from the same commit. Changed to display versions hierarchically grouped by commit SHA. - Show version numbers (v1, v2) only when multiple versions exist within same commit - Display GitCommit icon and commit SHA in group header - Use date+time format for detailed distinction
1 parent b217af4 commit 42b852b

1 file changed

Lines changed: 97 additions & 39 deletions

File tree

src/frontend/features/spec-view/components/executive-summary.tsx

Lines changed: 97 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Check,
55
ChevronDown,
66
FileText,
7+
GitCommit,
78
Globe,
89
History,
910
Languages,
@@ -41,6 +42,12 @@ import { calculateDocumentStats } from "../utils/stats";
4142

4243
type VersionInfoWithCommit = VersionInfo | RepoVersionInfo;
4344

45+
type VersionGroup = {
46+
commitSha: string;
47+
isLatest: boolean;
48+
versions: RepoVersionInfo[];
49+
};
50+
4451
type ExecutiveSummaryProps = {
4552
behaviorCacheStats?: BehaviorCacheStats;
4653
commitSha?: string;
@@ -65,8 +72,47 @@ const formatShortDate = (dateString: string, locale: string): string => {
6572
});
6673
};
6774

75+
const formatShortDateTime = (dateString: string, locale: string): string => {
76+
return new Date(dateString).toLocaleString(locale, {
77+
day: "numeric",
78+
hour: "2-digit",
79+
minute: "2-digit",
80+
month: "short",
81+
});
82+
};
83+
6884
const formatCommitSha = (sha: string): string => (sha.length < 7 ? sha : sha.slice(0, 7));
6985

86+
const groupVersionsByCommit = (
87+
versions: VersionInfoWithCommit[],
88+
latestDocumentId?: string
89+
): VersionGroup[] => {
90+
const groups = new Map<string, RepoVersionInfo[]>();
91+
92+
for (const version of versions) {
93+
if (!("commitSha" in version)) continue;
94+
const sha = version.commitSha;
95+
if (!groups.has(sha)) {
96+
groups.set(sha, []);
97+
}
98+
groups.get(sha)!.push(version);
99+
}
100+
101+
const result: VersionGroup[] = [];
102+
let isFirst = true;
103+
104+
for (const [commitSha, groupVersions] of groups) {
105+
result.push({
106+
commitSha,
107+
isLatest: isFirst && groupVersions.some((v) => v.id === latestDocumentId),
108+
versions: groupVersions,
109+
});
110+
isFirst = false;
111+
}
112+
113+
return result;
114+
};
115+
70116
export const ExecutiveSummary = ({
71117
behaviorCacheStats,
72118
commitSha,
@@ -93,6 +139,7 @@ export const ExecutiveSummary = ({
93139
const availableLanguages = document.availableLanguages ?? [];
94140
const isDisabled = isRegenerating || isGeneratingOtherLanguage;
95141
const isLatestVersion = latestDocumentId === undefined || document.id === latestDocumentId;
142+
const versionGroups = groupVersionsByCommit(versions, latestDocumentId);
96143

97144
// Build sets for available vs new languages
98145
const availableLanguageSet = new Set(availableLanguages.map((l) => l.language));
@@ -251,50 +298,61 @@ export const ExecutiveSummary = ({
251298
</TooltipTrigger>
252299
<TooltipContent>{t("executiveSummary.switchVersionTooltip")}</TooltipContent>
253300
</Tooltip>
254-
<DropdownMenuContent align="end" className="w-56 max-h-80 overflow-y-auto">
301+
<DropdownMenuContent align="end" className="w-64 max-h-80 overflow-y-auto">
255302
<DropdownMenuLabel className="flex items-center gap-2 px-2 py-2 text-[11px] font-medium uppercase tracking-wider text-muted-foreground/70 bg-muted/40 -mx-1 mb-1 border-b border-border/50">
256303
<History className="h-3 w-3" />
257304
{t("executiveSummary.versionHistory")}
258305
</DropdownMenuLabel>
259-
{versions.map((versionInfo) => {
260-
const isCurrentVersion = versionInfo.version === currentVersion;
261-
// Use document ID for latest check (createdAt DESC sort order)
262-
const isLatest = "id" in versionInfo && versionInfo.id === latestDocumentId;
263-
const versionCommitSha =
264-
"commitSha" in versionInfo ? versionInfo.commitSha : undefined;
265-
return (
266-
<DropdownMenuItem
267-
className={cn(
268-
"flex items-center justify-between",
269-
isCurrentVersion && "bg-muted font-medium"
270-
)}
271-
key={versionInfo.version}
272-
onClick={() => onVersionSwitch(versionInfo.version)}
273-
>
274-
<div className="flex items-center gap-2">
275-
{isCurrentVersion ? (
276-
<Check className="h-3.5 w-3.5 text-primary" />
277-
) : (
278-
<span className="w-3.5" />
279-
)}
280-
<span>{formatShortDate(versionInfo.createdAt, locale)}</span>
281-
{versionCommitSha && (
282-
<code className="text-[10px] text-muted-foreground/70 font-mono">
283-
{formatCommitSha(versionCommitSha)}
284-
</code>
285-
)}
286-
{isLatest && (
287-
<span className="text-muted-foreground">
288-
({t("executiveSummary.latestLabel")})
289-
</span>
290-
)}
306+
{versionGroups.map((group, groupIndex) => (
307+
<div key={group.commitSha}>
308+
{/* Commit group header */}
309+
<div className="flex items-center justify-between px-2 py-1.5 text-xs">
310+
<div className="flex items-center gap-1.5 text-muted-foreground">
311+
<GitCommit className="h-3 w-3" />
312+
<code className="font-mono">{formatCommitSha(group.commitSha)}</code>
291313
</div>
292-
<span className="text-xs text-muted-foreground">
293-
{t("executiveSummary.versionLabel", { version: versionInfo.version })}
294-
</span>
295-
</DropdownMenuItem>
296-
);
297-
})}
314+
{group.isLatest && (
315+
<span className="text-[10px] text-muted-foreground">
316+
{t("executiveSummary.latestLabel")}
317+
</span>
318+
)}
319+
</div>
320+
321+
{/* Version items */}
322+
{group.versions.map((versionInfo) => {
323+
const isCurrentVersion = versionInfo.version === currentVersion;
324+
return (
325+
<DropdownMenuItem
326+
className={cn(
327+
"flex items-center justify-between ml-4",
328+
isCurrentVersion && "bg-muted font-medium"
329+
)}
330+
key={versionInfo.id}
331+
onClick={() => onVersionSwitch(versionInfo.version)}
332+
>
333+
<div className="flex items-center gap-2">
334+
{isCurrentVersion ? (
335+
<Check className="h-3.5 w-3.5 text-primary" />
336+
) : (
337+
<span className="w-3.5" />
338+
)}
339+
<span>{formatShortDateTime(versionInfo.createdAt, locale)}</span>
340+
</div>
341+
{group.versions.length > 1 && (
342+
<span className="text-xs text-muted-foreground">
343+
{t("executiveSummary.versionLabel", {
344+
version: versionInfo.version,
345+
})}
346+
</span>
347+
)}
348+
</DropdownMenuItem>
349+
);
350+
})}
351+
352+
{/* Separator between groups */}
353+
{groupIndex < versionGroups.length - 1 && <DropdownMenuSeparator />}
354+
</div>
355+
))}
298356
</DropdownMenuContent>
299357
</DropdownMenu>
300358
) : (

0 commit comments

Comments
 (0)