Skip to content

Commit 7b90e56

Browse files
authored
Merge pull request #15 from RealZST/fix/install-dialog-lint
fix: resolve lint errors and fix infinite loop in NewSkillsDialog
2 parents 2043900 + 1803639 commit 7b90e56

31 files changed

Lines changed: 1511 additions & 648 deletions

src/App.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { getCurrentWindow } from "@tauri-apps/api/window";
22
import { useEffect, useRef, useState } from "react";
33
import { HashRouter, Navigate, Route, Routes } from "react-router-dom";
44
import { AppShell } from "./components/layout/app-shell";
5+
import { UpdateDialog } from "./components/layout/update-dialog";
56
import { Confetti } from "./components/onboarding/confetti";
67
import { Onboarding, useOnboarding } from "./components/onboarding/onboarding";
7-
import { api } from "./lib/invoke";
88
import { ErrorBoundary } from "./components/shared/error-boundary";
9-
import { UpdateDialog } from "./components/layout/update-dialog";
9+
import { api } from "./lib/invoke";
1010
import AgentsPage from "./pages/agents";
1111
import AuditPage from "./pages/audit";
1212
import ExtensionsPage from "./pages/extensions";
@@ -15,8 +15,8 @@ import OverviewPage from "./pages/overview";
1515
import SettingsPage from "./pages/settings";
1616
import { useAuditStore } from "./stores/audit-store";
1717
import { useExtensionStore } from "./stores/extension-store";
18-
import { useUpdateStore } from "./stores/update-store";
1918
import { resolveMode, useUIStore } from "./stores/ui-store";
19+
import { useUpdateStore } from "./stores/update-store";
2020

2121
/** Minimum interval (ms) between consecutive scan_and_sync calls */
2222
const SCAN_DEBOUNCE_MS = 5_000;

src/components/agents/config-file-entry.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { clsx } from "clsx";
2-
import { useScrollPassthrough } from "@/hooks/use-scroll-passthrough";
32
import {
43
Check,
54
ChevronRight,
@@ -13,6 +12,7 @@ import {
1312
X,
1413
} from "lucide-react";
1514
import { useEffect, useState } from "react";
15+
import { useScrollPassthrough } from "@/hooks/use-scroll-passthrough";
1616
import { openDirectoryPicker, openFilePicker } from "@/lib/dialog";
1717
import type { AgentConfigFile } from "@/lib/types";
1818
import { useAgentConfigStore } from "@/stores/agent-config-store";
@@ -122,7 +122,10 @@ export function ConfigFileEntry({ file }: { file: AgentConfigFile }) {
122122
{previewError}
123123
</div>
124124
) : preview !== null ? (
125-
<pre onWheel={handleNestedWheel} className="text-[11px] leading-relaxed text-muted-foreground font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto mb-3">
125+
<pre
126+
onWheel={handleNestedWheel}
127+
className="text-[11px] leading-relaxed text-muted-foreground font-mono whitespace-pre-wrap max-h-[200px] overflow-y-auto mb-3"
128+
>
126129
{preview || (file.is_dir ? "(empty directory)" : "(empty file)")}
127130
</pre>
128131
) : (
@@ -207,7 +210,11 @@ export function ConfigFileEntry({ file }: { file: AgentConfigFile }) {
207210
}}
208211
className="inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-2.5 py-1 text-[11px] font-medium transition-colors hover:bg-accent"
209212
>
210-
{file.is_dir ? <FolderOpen size={12} /> : <FileSearch size={12} />}{" "}
213+
{file.is_dir ? (
214+
<FolderOpen size={12} />
215+
) : (
216+
<FileSearch size={12} />
217+
)}{" "}
211218
{file.is_dir ? "Reveal in Finder" : "Open in Editor"}
212219
</button>
213220
{!file.is_dir && (

src/components/extensions/delete-dialog.tsx

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { AlertTriangle, FolderOpen, Link, Loader2, Trash2 } from "lucide-react";
22
import { useEffect, useRef } from "react";
33
import { useFocusTrap } from "@/hooks/use-focus-trap";
44
import type {
5-
Extension,
65
ExtensionContent as ExtContent,
6+
Extension,
77
GroupedExtension,
88
} from "@/lib/types";
99
import { agentDisplayName } from "@/lib/types";
@@ -114,16 +114,20 @@ export function DeleteDialog({
114114
setDeleteAgents(new Set());
115115
}, [setDeleteAgents]);
116116

117-
const displayName = group.kind === "hook"
118-
? (() => {
119-
const parts = group.name.split(":");
120-
if (parts.length >= 3) {
121-
const cmd = parts.slice(2).join(":");
122-
return cmd.split(" ").map((t) => t.split("/").pop() || t).join(" ");
123-
}
124-
return group.name;
125-
})()
126-
: group.name;
117+
const displayName =
118+
group.kind === "hook"
119+
? (() => {
120+
const parts = group.name.split(":");
121+
if (parts.length >= 3) {
122+
const cmd = parts.slice(2).join(":");
123+
return cmd
124+
.split(" ")
125+
.map((t) => t.split("/").pop() || t)
126+
.join(" ");
127+
}
128+
return group.name;
129+
})()
130+
: group.name;
127131

128132
const isCli = group.kind === "cli";
129133

@@ -134,14 +138,17 @@ export function DeleteDialog({
134138
const childMap = new Map<string, { name: string; kind: string }>();
135139
for (const child of childExtensions ?? []) {
136140
const key = `${child.kind}:${child.name}`;
137-
if (!childMap.has(key)) childMap.set(key, { name: child.name, kind: child.kind });
141+
if (!childMap.has(key))
142+
childMap.set(key, { name: child.name, kind: child.kind });
138143
}
139144
const children = [...childMap.values()];
140145

141146
return (
142147
<div
143148
className="absolute inset-0 z-50 flex items-center justify-center rounded-xl overflow-hidden"
144-
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
149+
onClick={(e) => {
150+
if (e.target === e.currentTarget) onClose();
151+
}}
145152
>
146153
<div className="absolute inset-0 bg-background/80 backdrop-blur-[2px]" />
147154
<div
@@ -174,7 +181,10 @@ export function DeleteDialog({
174181
</p>
175182
<div className="space-y-1 rounded-lg border border-border bg-muted/30 p-2.5">
176183
{children.map((child) => (
177-
<div key={`${child.kind}:${child.name}`} className="flex items-center gap-2 text-xs">
184+
<div
185+
key={`${child.kind}:${child.name}`}
186+
className="flex items-center gap-2 text-xs"
187+
>
178188
<span className="shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium uppercase text-muted-foreground">
179189
{child.kind}
180190
</span>
@@ -189,7 +199,8 @@ export function DeleteDialog({
189199
<div className="flex items-start gap-1.5 rounded-lg border border-chart-5/30 bg-chart-5/5 p-2.5 text-xs text-chart-5">
190200
<AlertTriangle size={12} className="mt-0.5 shrink-0" />
191201
<span>
192-
The binary <span className="font-mono">{binaryPath}</span> will also be removed.
202+
The binary <span className="font-mono">{binaryPath}</span>{" "}
203+
will also be removed.
193204
</span>
194205
</div>
195206
)}
@@ -225,11 +236,12 @@ export function DeleteDialog({
225236
const usePathBased = isSkill && skillLocations && skillLocations.length > 0;
226237

227238
const items: DeleteItem[] = usePathBased
228-
? buildPathItems(skillLocations!)
239+
? buildPathItems(skillLocations)
229240
: buildAgentItems(group.instances, instanceData, group.kind, group.name);
230241

231242
const selectedKeys = deleteAgents;
232-
const allSelected = items.length > 0 && items.every((i) => selectedKeys.has(i.key));
243+
const allSelected =
244+
items.length > 0 && items.every((i) => selectedKeys.has(i.key));
233245
const isSingle = items.length === 1;
234246

235247
return (
@@ -333,11 +345,12 @@ export function DeleteDialog({
333345
<span className="break-all">{p}</span>
334346
</p>
335347
))}
336-
{!item.description && item.mcps.map((name) => (
337-
<p key={name} className="text-muted-foreground mt-0.5">
338-
MCP: {name}
339-
</p>
340-
))}
348+
{!item.description &&
349+
item.mcps.map((name) => (
350+
<p key={name} className="text-muted-foreground mt-0.5">
351+
MCP: {name}
352+
</p>
353+
))}
341354
{item.symlink && (
342355
<p className="flex items-center gap-1 text-chart-5 mt-0.5">
343356
<Link size={10} className="shrink-0" />
@@ -351,13 +364,18 @@ export function DeleteDialog({
351364

352365
{/* Symlink warnings */}
353366
{(() => {
354-
const selected = isSingle ? items : items.filter((i) => selectedKeys.has(i.key));
367+
const selected = isSingle
368+
? items
369+
: items.filter((i) => selectedKeys.has(i.key));
355370
const warnings: React.ReactNode[] = [];
356371

357372
const symlinkItems = selected.filter((i) => i.symlink);
358373
if (symlinkItems.length > 0) {
359374
warnings.push(
360-
<div key="symlink" className="flex items-start gap-1.5 rounded-lg border border-chart-5/30 bg-chart-5/5 p-2.5 text-xs text-chart-5">
375+
<div
376+
key="symlink"
377+
className="flex items-start gap-1.5 rounded-lg border border-chart-5/30 bg-chart-5/5 p-2.5 text-xs text-chart-5"
378+
>
361379
<AlertTriangle size={12} className="mt-0.5 shrink-0" />
362380
<span>
363381
{symlinkItems.length === 1
@@ -377,23 +395,33 @@ export function DeleteDialog({
377395

378396
const selectedPaths = new Set(selected.flatMap((i) => i.paths));
379397
const affectedSymlinks = items.filter(
380-
(i) => i.symlink && selectedPaths.has(i.symlink) && !selected.includes(i),
398+
(i) =>
399+
i.symlink &&
400+
selectedPaths.has(i.symlink) &&
401+
!selected.includes(i),
381402
);
382403
if (affectedSymlinks.length > 0) {
383404
const affectedAgents = affectedSymlinks.flatMap((i) => i.agents);
384405
warnings.push(
385-
<div key="broken-symlink" className="flex items-start gap-1.5 rounded-lg border border-chart-5/30 bg-chart-5/5 p-2.5 text-xs text-chart-5">
406+
<div
407+
key="broken-symlink"
408+
className="flex items-start gap-1.5 rounded-lg border border-chart-5/30 bg-chart-5/5 p-2.5 text-xs text-chart-5"
409+
>
386410
<AlertTriangle size={12} className="mt-0.5 shrink-0" />
387411
<span>
388412
{affectedAgents.map(agentDisplayName).join(", ")}{" "}
389-
{affectedAgents.length === 1 ? "has a symlink" : "have symlinks"}{" "}
390-
pointing to this path — {affectedAgents.length === 1 ? "it" : "they"} will become invalid.
413+
{affectedAgents.length === 1
414+
? "has a symlink"
415+
: "have symlinks"}{" "}
416+
pointing to this path —{" "}
417+
{affectedAgents.length === 1 ? "it" : "they"} will become
418+
invalid.
391419
</span>
392420
</div>,
393421
);
394422
}
395423

396-
return warnings.length > 0 ? <>{warnings}</> : null;
424+
return warnings.length > 0 ? warnings : null;
397425
})()}
398426

399427
{/* Delete button */}
@@ -408,8 +436,7 @@ export function DeleteDialog({
408436
) : (
409437
<Trash2 size={12} />
410438
)}
411-
Delete from{" "}
412-
{items[0].agents.map(agentDisplayName).join(", ")}
439+
Delete from {items[0].agents.map(agentDisplayName).join(", ")}
413440
</button>
414441
) : (
415442
<button
@@ -430,7 +457,8 @@ export function DeleteDialog({
430457
) : (
431458
<Trash2 size={12} />
432459
)}
433-
Remove {selectedKeys.size} item{selectedKeys.size !== 1 ? "s" : ""}
460+
Remove {selectedKeys.size} item
461+
{selectedKeys.size !== 1 ? "s" : ""}
434462
</button>
435463
)}
436464
</div>

src/components/extensions/detail-cli-sections.tsx

Lines changed: 63 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,33 @@ interface CliSectionsProps {
99
}
1010

1111
export function CliSections({ group, extensions }: CliSectionsProps) {
12-
if (group.kind !== "cli") return null;
13-
1412
const setSelectedId = useExtensionStore((s) => s.setSelectedId);
1513
const grouped = useExtensionStore((s) => s.grouped);
1614

17-
const children = findCliChildren(extensions, group.instances[0]?.id, group.pack);
15+
if (group.kind !== "cli") return null;
16+
17+
const children = findCliChildren(
18+
extensions,
19+
group.instances[0]?.id,
20+
group.pack,
21+
);
1822

1923
// Deduplicate children by groupKey so each child skill/MCP appears once
2024
const allGroups = grouped();
21-
const childGroups = new Map<string, { name: string; kind: ExtensionKind; groupKey: string }>();
25+
const childGroups = new Map<
26+
string,
27+
{ name: string; kind: ExtensionKind; groupKey: string }
28+
>();
2229
for (const child of children) {
2330
const key = extensionGroupKey(child);
2431
if (!childGroups.has(key)) {
2532
const exists = allGroups.some((g) => g.groupKey === key);
2633
if (exists) {
27-
childGroups.set(key, { name: child.name, kind: child.kind, groupKey: key });
34+
childGroups.set(key, {
35+
name: child.name,
36+
kind: child.kind,
37+
groupKey: key,
38+
});
2839
}
2940
}
3041
}
@@ -34,7 +45,7 @@ export function CliSections({ group, extensions }: CliSectionsProps) {
3445
{/* CLI Details */}
3546
{group.instances[0]?.cli_meta &&
3647
(() => {
37-
const cli_meta = group.instances[0].cli_meta!;
48+
const cli_meta = group.instances[0].cli_meta;
3849
return (
3950
<div className="mt-4 space-y-3 text-sm">
4051
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
@@ -92,42 +103,54 @@ export function CliSections({ group, extensions }: CliSectionsProps) {
92103
})()}
93104

94105
{/* Associated Extensions — grouped by kind in cards */}
95-
{childGroups.size > 0 && (() => {
96-
const byKind = new Map<ExtensionKind, { name: string; kind: ExtensionKind; groupKey: string }[]>();
97-
for (const child of childGroups.values()) {
98-
const list = byKind.get(child.kind) ?? [];
99-
list.push(child);
100-
byKind.set(child.kind, list);
101-
}
102-
const kindLabel: Record<string, string> = { skill: "Skills", mcp: "MCP Servers", plugin: "Plugins", hook: "Hooks" };
103-
return (
104-
<div className="mt-4">
105-
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">
106-
Associated Extensions
107-
</h4>
108-
<div className="space-y-2">
109-
{[...byKind.entries()].map(([kind, items]) => (
110-
<div key={kind} className="rounded-lg border border-border bg-card p-3">
111-
<span className="text-xs font-medium text-muted-foreground">
112-
{kindLabel[kind] ?? kind} ({items.length})
113-
</span>
114-
<div className="mt-2 flex flex-wrap gap-1">
115-
{items.map((child) => (
116-
<button
117-
key={child.groupKey}
118-
onClick={() => setSelectedId(child.groupKey)}
119-
className="rounded-md bg-muted/50 px-2 py-1 text-xs text-foreground hover:bg-accent transition-colors"
120-
>
121-
{child.name}
122-
</button>
123-
))}
106+
{childGroups.size > 0 &&
107+
(() => {
108+
const byKind = new Map<
109+
ExtensionKind,
110+
{ name: string; kind: ExtensionKind; groupKey: string }[]
111+
>();
112+
for (const child of childGroups.values()) {
113+
const list = byKind.get(child.kind) ?? [];
114+
list.push(child);
115+
byKind.set(child.kind, list);
116+
}
117+
const kindLabel: Record<string, string> = {
118+
skill: "Skills",
119+
mcp: "MCP Servers",
120+
plugin: "Plugins",
121+
hook: "Hooks",
122+
};
123+
return (
124+
<div className="mt-4">
125+
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground mb-2">
126+
Associated Extensions
127+
</h4>
128+
<div className="space-y-2">
129+
{[...byKind.entries()].map(([kind, items]) => (
130+
<div
131+
key={kind}
132+
className="rounded-lg border border-border bg-card p-3"
133+
>
134+
<span className="text-xs font-medium text-muted-foreground">
135+
{kindLabel[kind] ?? kind} ({items.length})
136+
</span>
137+
<div className="mt-2 flex flex-wrap gap-1">
138+
{items.map((child) => (
139+
<button
140+
key={child.groupKey}
141+
onClick={() => setSelectedId(child.groupKey)}
142+
className="rounded-md bg-muted/50 px-2 py-1 text-xs text-foreground hover:bg-accent transition-colors"
143+
>
144+
{child.name}
145+
</button>
146+
))}
147+
</div>
124148
</div>
125-
</div>
126-
))}
149+
))}
150+
</div>
127151
</div>
128-
</div>
129-
);
130-
})()}
152+
);
153+
})()}
131154
</>
132155
);
133156
}

0 commit comments

Comments
 (0)