diff --git a/vscode/package.json b/vscode/package.json index 7d761a5..c947426 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,7 @@ "publisher": "clurdra", "name": "superpowers-vscode-clurdra", "displayName": "Superpowers-clurdra", - "version": "0.2.111", + "version": "0.2.112", "packageManager": "pnpm@10.27.0", "description": "Superpowers specs and plans Kanban explorer", "author": "clurdra", diff --git a/vscode/src/panel/handlers/release.ts b/vscode/src/panel/handlers/release.ts index cc93845..8f2ec2a 100644 --- a/vscode/src/panel/handlers/release.ts +++ b/vscode/src/panel/handlers/release.ts @@ -113,6 +113,7 @@ export async function handleReleaseCherryPick(panel: KanbanWebviewPanel): Promis type: 'release/pick-show', branch: current, groups: groupCommitsByPr(attachPulls(commits, index)), + firstAfterReleaseSha: commits.at(-1)?.sha, }) } catch (err) { diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index 8b57807..535bbf7 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -124,7 +124,7 @@ export type ExtensionToWebview running: boolean jenkinsConfigured: boolean } - | { type: 'release/pick-show', branch: string, groups: ReleaseCommitGroup[] } + | { type: 'release/pick-show', branch: string, groups: ReleaseCommitGroup[], firstAfterReleaseSha?: string } | { type: 'env-lock/status', locked: boolean, fileCount: number, failedCount?: number } | { type: 'issue/remove', issueNumber: number } | { type: 'profiles/show', data: ProfilesData } diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index a0315c2..e0dac38 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -501,6 +501,7 @@ export function App() { open={cherryPick !== null} branch={cherryPick?.branch ?? ''} groups={cherryPick?.groups ?? []} + firstAfterReleaseSha={cherryPick?.firstAfterReleaseSha} onCancel={dismissCherryPick} onConfirm={confirmCherryPick} /> diff --git a/vscode/webview-ui/src/components/CherryPickModal.tsx b/vscode/webview-ui/src/components/CherryPickModal.tsx index 23cf607..ac7a5b6 100644 --- a/vscode/webview-ui/src/components/CherryPickModal.tsx +++ b/vscode/webview-ui/src/components/CherryPickModal.tsx @@ -1,11 +1,13 @@ import type { ReleaseCommitGroup } from '../lib/messages' import { ChevronRight, X } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' +import { filterCherryPickGroups, groupContainingSha, uniqueAuthors } from '../lib/cherryPickView' interface Props { open: boolean branch: string groups: ReleaseCommitGroup[] + firstAfterReleaseSha?: string onCancel: () => void onConfirm: (shas: string[]) => void } @@ -14,38 +16,25 @@ function groupKey(g: ReleaseCommitGroup): string { return g.pr != null ? `pr-${g.pr}` : 'ungrouped' } -function filterGroups(groups: ReleaseCommitGroup[], query: string): ReleaseCommitGroup[] { - const q = query.trim().toLowerCase() - if (!q) - return groups - const out: ReleaseCommitGroup[] = [] - for (const g of groups) { - const head = `#${g.pr ?? ''} ${g.title} ${g.authors.join(' ')}`.toLowerCase() - if (head.includes(q)) { - out.push(g) - continue - } - const commits = g.commits.filter(c => - `${c.subject} ${c.author} ${c.sha} ${c.date}`.toLowerCase().includes(q), - ) - if (commits.length > 0) - out.push({ ...g, commits }) - } - return out -} - -export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: Props) { +export function CherryPickModal({ open, branch, groups, firstAfterReleaseSha, onCancel, onConfirm }: Props) { + const authors = useMemo(() => uniqueAuthors(groups), [groups]) const [query, setQuery] = useState('') const [selected, setSelected] = useState>(() => new Set()) const [expanded, setExpanded] = useState>(() => new Set()) + const [enabledAuthors, setEnabledAuthors] = useState>(() => new Set()) useEffect(() => { if (!open) return setQuery('') setSelected(new Set()) - setExpanded(new Set(groups.filter(g => g.pr == null).map(groupKey))) - }, [open, groups]) + setEnabledAuthors(new Set(uniqueAuthors(groups))) + const keys = new Set(groups.filter(g => g.pr == null).map(groupKey)) + const marked = groupContainingSha(groups, firstAfterReleaseSha) + if (marked) + keys.add(groupKey(marked)) + setExpanded(keys) + }, [open, groups, firstAfterReleaseSha]) useEffect(() => { if (!open) @@ -58,7 +47,21 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P return () => document.removeEventListener('keydown', onKey) }, [open, onCancel]) - const visible = useMemo(() => filterGroups(groups, query), [groups, query]) + const visible = useMemo( + () => filterCherryPickGroups(groups, query, enabledAuthors), + [groups, query, enabledAuthors], + ) + + function toggleAuthor(name: string): void { + setEnabledAuthors((prev) => { + const next = new Set(prev) + if (next.has(name)) + next.delete(name) + else + next.add(name) + return next + }) + } if (!open) return null @@ -119,13 +122,27 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P -
+
setQuery(e.target.value)} - placeholder="搜索作者、PR 号或标题" + placeholder="搜索 PR 号或标题" className="w-full rounded border border-[var(--vscode-input-border,transparent)] bg-[var(--vscode-input-background)] px-2 py-1.5 text-xs outline-none" /> + {authors.length > 0 && ( +
+ {authors.map(name => ( + + ))} +
+ )}
{visible.length === 0 @@ -172,7 +189,7 @@ export function CherryPickModal({ open, branch, groups, onCancel, onConfirm }: P
    {g.commits.map(c => (
  • -