From 2f603e1e8cc720fcf40a300051d04dadb0417cde Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Tue, 31 Mar 2026 23:52:31 -0500 Subject: [PATCH 1/2] Polish skill detail dialog layout - Add icon, tag chips, and improved command/path presentation - Make dialog content and skills page scrollable within available height --- apps/web/src/components/skills/SkillsPage.tsx | 55 +++++++++++++------ apps/web/src/components/ui/dialog.tsx | 8 ++- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/apps/web/src/components/skills/SkillsPage.tsx b/apps/web/src/components/skills/SkillsPage.tsx index a7f3f2a2d..88ec65f9c 100644 --- a/apps/web/src/components/skills/SkillsPage.tsx +++ b/apps/web/src/components/skills/SkillsPage.tsx @@ -138,30 +138,53 @@ function SkillDetailDialog(props: { const mutable = isCatalog ? !skill.immutable && skill.installed : skill.mutable; const pathValue = skill.path; const slashName = isCatalog ? skill.name.toLowerCase().replace(/\s+/g, "-") : skill.name; + const tags = "tags" in skill ? skill.tags : []; + const Icon = skillIcon("icon" in skill ? skill.icon : "file"); return ( - {skill.name} - {skill.description} - - -
- {("tags" in skill ? skill.tags : []).map((tag) => ( - - {tag} - - ))} +
+
+ +
+
+ {skill.name} + {skill.description} +
+ {tags.length > 0 && ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ )} + +
-

Slash commands

-

/{slashName}

-

/skill read {slashName}

+

+ Slash commands +

+
+

+ /{slashName} +

+

+ /skill read {slashName} +

+
{pathValue ? (
-

Path

-

{pathValue}

+

+ Installed path +

+

+ {pathValue} +

) : null}
@@ -422,7 +445,7 @@ export function SkillsPage(props: {
-
+
diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx index 774047fee..73ae52e11 100644 --- a/apps/web/src/components/ui/dialog.tsx +++ b/apps/web/src/components/ui/dialog.tsx @@ -90,7 +90,13 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { } function DialogPanel({ className, ...props }: React.ComponentProps<"div">) { - return
; + return ( +
+ ); } function DialogFooter({ From 5c5e373c38c71c22077128d4ed7d2a3e2f6876f4 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Wed, 1 Apr 2026 02:32:05 -0500 Subject: [PATCH 2/2] Add accept-all diff review action --- apps/web/src/components/DiffPanel.tsx | 29 ++++++++++++++++++++ apps/web/src/lib/diffFileReviewState.test.ts | 26 ++++++++++++++++++ apps/web/src/lib/diffFileReviewState.ts | 24 ++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 211df4ef2..daaa64040 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -14,6 +14,7 @@ import { parseDiffRouteSearch, stripDiffSearchParams } from "../diffRouteSearch" import { useTheme } from "../hooks/useTheme"; import { buildPatchCacheKey } from "../lib/diffRendering"; import { + acceptAllDiffFiles, expandDiffFile, reconcileDiffFileReviewState, setDiffFileContextMode, @@ -512,6 +513,16 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { const activeReviewState = patchReviewSelectionKey ? (reviewStateBySelectionKey[patchReviewSelectionKey] ?? {}) : {}; + const acceptedFileCount = useMemo( + () => + renderableFilePaths.reduce( + (count, filePath) => count + (activeReviewState[filePath]?.accepted ? 1 : 0), + 0, + ), + [activeReviewState, renderableFilePaths], + ); + const hasUnacceptedFiles = + renderableFilePaths.length > 0 && acceptedFileCount < renderableFilePaths.length; useEffect(() => { if (diffOpen && !previousDiffOpenRef.current) { @@ -595,6 +606,12 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) { }, [updateActiveReviewState], ); + const onAcceptAllFiles = useCallback(() => { + if (renderableFilePaths.length === 0) { + return; + } + updateActiveReviewState((current) => acceptAllDiffFiles(current, renderableFilePaths)); + }, [renderableFilePaths, updateActiveReviewState]); const onToggleFileCollapsed = useCallback( (filePath: string) => { updateActiveReviewState((current) => toggleDiffFileCollapsed(current, filePath)); @@ -704,6 +721,18 @@ export default function DiffPanel({ mode = "inline" }: DiffPanelProps) {
+ {renderablePatch?.kind === "files" ? ( + + ) : null} { }); }); +describe("acceptAllDiffFiles", () => { + it("marks every listed file accepted and collapsed", () => { + expect( + acceptAllDiffFiles( + { + "src/a.ts": { accepted: false, collapsed: false, contextMode: "full" }, + }, + ["src/a.ts", "src/b.ts"], + ), + ).toEqual({ + "src/a.ts": { accepted: true, collapsed: true, contextMode: "full" }, + "src/b.ts": { accepted: true, collapsed: true, contextMode: "patch" }, + }); + }); + + it("returns the same object when every listed file is already accepted", () => { + const state = { + "src/a.ts": { accepted: true, collapsed: true, contextMode: "patch" as const }, + "src/b.ts": { accepted: true, collapsed: true, contextMode: "full" as const }, + }; + + expect(acceptAllDiffFiles(state, ["src/a.ts", "src/b.ts"])).toBe(state); + }); +}); + describe("toggleDiffFileCollapsed", () => { it("toggles collapsed without changing acceptance", () => { expect( diff --git a/apps/web/src/lib/diffFileReviewState.ts b/apps/web/src/lib/diffFileReviewState.ts index 7e15789ce..1fc22d12a 100644 --- a/apps/web/src/lib/diffFileReviewState.ts +++ b/apps/web/src/lib/diffFileReviewState.ts @@ -39,6 +39,30 @@ export function toggleDiffFileAccepted( }; } +export function acceptAllDiffFiles( + current: DiffFileReviewStateByPath, + paths: ReadonlyArray, +): DiffFileReviewStateByPath { + let next = current; + + for (const path of paths) { + const previous = next[path] ?? DEFAULT_DIFF_FILE_REVIEW_STATE; + if (previous.accepted && previous.collapsed) { + continue; + } + if (next === current) { + next = { ...current }; + } + next[path] = { + accepted: true, + collapsed: true, + contextMode: previous.contextMode, + }; + } + + return next; +} + export function toggleDiffFileCollapsed( current: DiffFileReviewStateByPath, path: string,