claude-smart 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/claude-smart.js +172 -18
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/app/api/config/route.ts +11 -2
- package/plugin/dashboard/app/api/health/route.ts +45 -6
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
- package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
- package/plugin/dashboard/app/configure/env/page.tsx +36 -31
- package/plugin/dashboard/app/configure/layout.tsx +1 -1
- package/plugin/dashboard/app/configure/server/page.tsx +8 -14
- package/plugin/dashboard/app/dashboard/page.tsx +311 -115
- package/plugin/dashboard/app/globals.css +80 -66
- package/plugin/dashboard/app/layout.tsx +13 -10
- package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
- package/plugin/dashboard/app/preferences/page.tsx +154 -54
- package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
- package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
- package/plugin/dashboard/app/sessions/page.tsx +14 -10
- package/plugin/dashboard/app/skills/page.tsx +175 -56
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
- package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
- package/plugin/dashboard/components/common/empty-state.tsx +4 -2
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +5 -3
- package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
- package/plugin/dashboard/components/common/stat-card.tsx +9 -5
- package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
- package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
- package/plugin/dashboard/components/ui/input.tsx +1 -0
- package/plugin/dashboard/hooks/use-settings.tsx +30 -61
- package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
- package/plugin/dashboard/lib/config-file.ts +2 -0
- package/plugin/dashboard/lib/reflexio-client.ts +23 -48
- package/plugin/dashboard/lib/session-reader.ts +222 -6
- package/plugin/dashboard/lib/types.ts +20 -1
- package/plugin/dashboard/package-lock.json +70 -95
- package/plugin/dashboard/package.json +5 -2
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/_lib.sh +126 -0
- package/plugin/scripts/backend-service.sh +32 -7
- package/plugin/scripts/cli.sh +4 -2
- package/plugin/scripts/codex-hook.js +100 -3
- package/plugin/scripts/dashboard-service.sh +98 -19
- package/plugin/scripts/hook_entry.sh +32 -11
- package/plugin/scripts/smart-install.sh +27 -44
- package/plugin/src/claude_smart/cli.py +204 -20
- package/plugin/src/claude_smart/context_format.py +244 -6
- package/plugin/src/claude_smart/context_inject.py +8 -1
- package/plugin/src/claude_smart/cs_cite.py +186 -34
- package/plugin/src/claude_smart/env_config.py +102 -0
- package/plugin/src/claude_smart/events/session_end.py +171 -6
- package/plugin/src/claude_smart/events/session_start.py +26 -2
- package/plugin/src/claude_smart/events/stop.py +48 -9
- package/plugin/src/claude_smart/hook.py +62 -4
- package/plugin/src/claude_smart/hook_log.py +301 -0
- package/plugin/src/claude_smart/internal_call.py +30 -0
- package/plugin/src/claude_smart/publish.py +5 -0
- package/plugin/src/claude_smart/reflexio_adapter.py +102 -26
- package/plugin/uv.lock +1 -1
|
@@ -5,7 +5,6 @@ import Link from "next/link";
|
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import {
|
|
7
7
|
ArrowLeft,
|
|
8
|
-
Trash2,
|
|
9
8
|
Save,
|
|
10
9
|
AlertTriangle,
|
|
11
10
|
Pencil,
|
|
@@ -20,12 +19,12 @@ import {
|
|
|
20
19
|
} from "lucide-react";
|
|
21
20
|
import { PageHeader } from "@/components/common/page-header";
|
|
22
21
|
import { EmptyState } from "@/components/common/empty-state";
|
|
22
|
+
import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
|
|
23
23
|
import { Button } from "@/components/ui/button";
|
|
24
24
|
import { Label } from "@/components/ui/label";
|
|
25
25
|
import { Badge } from "@/components/ui/badge";
|
|
26
26
|
import { Separator } from "@/components/ui/separator";
|
|
27
27
|
import { reflexio } from "@/lib/reflexio-client";
|
|
28
|
-
import { useSettings } from "@/hooks/use-settings";
|
|
29
28
|
import { formatTimestamp, truncateId } from "@/lib/format";
|
|
30
29
|
import { cn } from "@/lib/utils";
|
|
31
30
|
import { statusLabel } from "@/lib/status";
|
|
@@ -54,7 +53,6 @@ export default function ProjectSkillDetailPage({
|
|
|
54
53
|
}) {
|
|
55
54
|
const { id } = use(params);
|
|
56
55
|
const router = useRouter();
|
|
57
|
-
const { reflexioUrl } = useSettings();
|
|
58
56
|
|
|
59
57
|
const [playbook, setPlaybook] = useState<UserPlaybook | null>(null);
|
|
60
58
|
const [notFound, setNotFound] = useState(false);
|
|
@@ -71,7 +69,7 @@ export default function ProjectSkillDetailPage({
|
|
|
71
69
|
useEffect(() => {
|
|
72
70
|
let cancelled = false;
|
|
73
71
|
reflexio
|
|
74
|
-
.getUserPlaybooks({
|
|
72
|
+
.getUserPlaybooks({})
|
|
75
73
|
.then((res) => {
|
|
76
74
|
if (cancelled) return;
|
|
77
75
|
const found = (res.user_playbooks ?? []).find(
|
|
@@ -90,7 +88,7 @@ export default function ProjectSkillDetailPage({
|
|
|
90
88
|
return () => {
|
|
91
89
|
cancelled = true;
|
|
92
90
|
};
|
|
93
|
-
}, [id
|
|
91
|
+
}, [id]);
|
|
94
92
|
|
|
95
93
|
const dirty = useMemo(() => {
|
|
96
94
|
if (!playbook) return false;
|
|
@@ -114,7 +112,6 @@ export default function ProjectSkillDetailPage({
|
|
|
114
112
|
trigger: form.trigger || null,
|
|
115
113
|
rationale: form.rationale || null,
|
|
116
114
|
},
|
|
117
|
-
reflexioUrl,
|
|
118
115
|
);
|
|
119
116
|
setPlaybook({
|
|
120
117
|
...playbook,
|
|
@@ -132,15 +129,9 @@ export default function ProjectSkillDetailPage({
|
|
|
132
129
|
|
|
133
130
|
const remove = async () => {
|
|
134
131
|
if (!playbook) return;
|
|
135
|
-
if (
|
|
136
|
-
!confirm(
|
|
137
|
-
`Delete project-specific skill #${playbook.user_playbook_id}? This cannot be undone.`,
|
|
138
|
-
)
|
|
139
|
-
)
|
|
140
|
-
return;
|
|
141
132
|
setDeleting(true);
|
|
142
133
|
try {
|
|
143
|
-
await reflexio.deleteUserPlaybook(playbook.user_playbook_id
|
|
134
|
+
await reflexio.deleteUserPlaybook(playbook.user_playbook_id);
|
|
144
135
|
router.push("/skills");
|
|
145
136
|
} catch (e) {
|
|
146
137
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -229,7 +220,7 @@ export default function ProjectSkillDetailPage({
|
|
|
229
220
|
<div className="mx-auto max-w-5xl grid gap-6 lg:grid-cols-[1fr_280px]">
|
|
230
221
|
<div className="space-y-6 min-w-0">
|
|
231
222
|
{error && (
|
|
232
|
-
<div className="rounded-
|
|
223
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm flex items-start gap-2">
|
|
233
224
|
<AlertTriangle className="h-4 w-4 mt-0.5 shrink-0" />
|
|
234
225
|
<span>{error}</span>
|
|
235
226
|
</div>
|
|
@@ -323,8 +314,8 @@ export default function ProjectSkillDetailPage({
|
|
|
323
314
|
|
|
324
315
|
{playbook && (
|
|
325
316
|
<aside className="space-y-3 lg:sticky lg:top-6 lg:self-start">
|
|
326
|
-
<div className="rounded-
|
|
327
|
-
<h3 className="text-xs font-semibold uppercase
|
|
317
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
318
|
+
<h3 className="text-xs font-semibold uppercase text-muted-foreground mb-3">
|
|
328
319
|
Metadata
|
|
329
320
|
</h3>
|
|
330
321
|
<dl className="space-y-2.5 text-sm">
|
|
@@ -365,8 +356,8 @@ export default function ProjectSkillDetailPage({
|
|
|
365
356
|
</div>
|
|
366
357
|
|
|
367
358
|
{playbook.source_interaction_ids?.length > 0 && (
|
|
368
|
-
<div className="rounded-
|
|
369
|
-
<h3 className="text-xs font-semibold uppercase
|
|
359
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
360
|
+
<h3 className="text-xs font-semibold uppercase text-muted-foreground mb-3">
|
|
370
361
|
Extracted from
|
|
371
362
|
</h3>
|
|
372
363
|
<p className="text-xs text-muted-foreground mb-2">
|
|
@@ -437,7 +428,7 @@ function Prose({ text, muted = false }: { text: string; muted?: boolean }) {
|
|
|
437
428
|
return (
|
|
438
429
|
<div
|
|
439
430
|
className={cn(
|
|
440
|
-
"rounded-
|
|
431
|
+
"rounded-lg border border-border bg-card px-4 py-3",
|
|
441
432
|
muted && "bg-muted/30",
|
|
442
433
|
)}
|
|
443
434
|
>
|
|
@@ -465,7 +456,7 @@ function AutoTextarea({
|
|
|
465
456
|
onChange={(e) => onChange(e.target.value)}
|
|
466
457
|
rows={rows}
|
|
467
458
|
placeholder={placeholder}
|
|
468
|
-
className="w-full rounded-
|
|
459
|
+
className="w-full rounded-lg border border-input bg-transparent px-4 py-3 text-sm leading-relaxed font-sans resize-y outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 placeholder:text-muted-foreground"
|
|
469
460
|
/>
|
|
470
461
|
);
|
|
471
462
|
}
|
|
@@ -575,23 +566,16 @@ function DangerZone({
|
|
|
575
566
|
disabled: boolean;
|
|
576
567
|
}) {
|
|
577
568
|
return (
|
|
578
|
-
<
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
onClick={onDelete}
|
|
590
|
-
disabled={deleting || disabled}
|
|
591
|
-
>
|
|
592
|
-
<Trash2 className="h-3.5 w-3.5" />
|
|
593
|
-
{deleting ? "Deleting…" : "Delete"}
|
|
594
|
-
</Button>
|
|
595
|
-
</section>
|
|
569
|
+
<DeleteLearningDangerZone
|
|
570
|
+
learningName="this project-specific skill"
|
|
571
|
+
description="Delete this claude-smart skill permanently."
|
|
572
|
+
consequences={[
|
|
573
|
+
"This skill will stop being injected into future sessions for this project.",
|
|
574
|
+
"Historical session records may still show prior uses.",
|
|
575
|
+
]}
|
|
576
|
+
onDelete={onDelete}
|
|
577
|
+
deleting={deleting}
|
|
578
|
+
disabled={disabled}
|
|
579
|
+
/>
|
|
596
580
|
);
|
|
597
581
|
}
|
|
@@ -5,7 +5,6 @@ import Link from "next/link";
|
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import {
|
|
7
7
|
ArrowLeft,
|
|
8
|
-
Trash2,
|
|
9
8
|
Save,
|
|
10
9
|
AlertTriangle,
|
|
11
10
|
Pencil,
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
} from "lucide-react";
|
|
21
20
|
import { PageHeader } from "@/components/common/page-header";
|
|
22
21
|
import { EmptyState } from "@/components/common/empty-state";
|
|
22
|
+
import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
|
|
23
23
|
import { Button } from "@/components/ui/button";
|
|
24
24
|
import { Label } from "@/components/ui/label";
|
|
25
25
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
SelectValue,
|
|
33
33
|
} from "@/components/ui/select";
|
|
34
34
|
import { reflexio } from "@/lib/reflexio-client";
|
|
35
|
-
import { useSettings } from "@/hooks/use-settings";
|
|
36
35
|
import { formatTimestamp, truncateId } from "@/lib/format";
|
|
37
36
|
import { cn } from "@/lib/utils";
|
|
38
37
|
import { agentPlaybookStatusLabel, statusLabel } from "@/lib/status";
|
|
@@ -85,7 +84,6 @@ export default function SharedSkillDetailPage({
|
|
|
85
84
|
}) {
|
|
86
85
|
const { id } = use(params);
|
|
87
86
|
const router = useRouter();
|
|
88
|
-
const { reflexioUrl } = useSettings();
|
|
89
87
|
|
|
90
88
|
const [playbook, setPlaybook] = useState<AgentPlaybook | null>(null);
|
|
91
89
|
const [notFound, setNotFound] = useState(false);
|
|
@@ -105,7 +103,7 @@ export default function SharedSkillDetailPage({
|
|
|
105
103
|
useEffect(() => {
|
|
106
104
|
let cancelled = false;
|
|
107
105
|
reflexio
|
|
108
|
-
.getAgentPlaybooks({
|
|
106
|
+
.getAgentPlaybooks({})
|
|
109
107
|
.then((res) => {
|
|
110
108
|
if (cancelled) return;
|
|
111
109
|
const found = (res.agent_playbooks ?? []).find(
|
|
@@ -124,7 +122,7 @@ export default function SharedSkillDetailPage({
|
|
|
124
122
|
return () => {
|
|
125
123
|
cancelled = true;
|
|
126
124
|
};
|
|
127
|
-
}, [id
|
|
125
|
+
}, [id]);
|
|
128
126
|
|
|
129
127
|
const dirty = useMemo(() => {
|
|
130
128
|
if (!playbook) return false;
|
|
@@ -150,7 +148,6 @@ export default function SharedSkillDetailPage({
|
|
|
150
148
|
rationale: form.rationale || null,
|
|
151
149
|
playbook_status: form.playbookStatus,
|
|
152
150
|
},
|
|
153
|
-
reflexioUrl,
|
|
154
151
|
);
|
|
155
152
|
setPlaybook({
|
|
156
153
|
...playbook,
|
|
@@ -186,7 +183,6 @@ export default function SharedSkillDetailPage({
|
|
|
186
183
|
agent_playbook_id: playbook.agent_playbook_id,
|
|
187
184
|
playbook_status: nextStatus,
|
|
188
185
|
},
|
|
189
|
-
reflexioUrl,
|
|
190
186
|
);
|
|
191
187
|
setPlaybook((current) =>
|
|
192
188
|
current ? { ...current, playbook_status: nextStatus } : current,
|
|
@@ -201,15 +197,9 @@ export default function SharedSkillDetailPage({
|
|
|
201
197
|
|
|
202
198
|
const remove = async () => {
|
|
203
199
|
if (!playbook) return;
|
|
204
|
-
if (
|
|
205
|
-
!confirm(
|
|
206
|
-
`Delete shared skill #${playbook.agent_playbook_id}? This cannot be undone.`,
|
|
207
|
-
)
|
|
208
|
-
)
|
|
209
|
-
return;
|
|
210
200
|
setDeleting(true);
|
|
211
201
|
try {
|
|
212
|
-
await reflexio.deleteAgentPlaybook(playbook.agent_playbook_id
|
|
202
|
+
await reflexio.deleteAgentPlaybook(playbook.agent_playbook_id);
|
|
213
203
|
router.push("/skills");
|
|
214
204
|
} catch (e) {
|
|
215
205
|
setError(e instanceof Error ? e.message : String(e));
|
|
@@ -299,7 +289,7 @@ export default function SharedSkillDetailPage({
|
|
|
299
289
|
<div className="mx-auto max-w-5xl grid gap-6 lg:grid-cols-[1fr_280px]">
|
|
300
290
|
<div className="space-y-6 min-w-0">
|
|
301
291
|
{error && (
|
|
302
|
-
<div className="rounded-
|
|
292
|
+
<div className="rounded-lg border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm flex items-start gap-2">
|
|
303
293
|
<AlertTriangle className="h-4 w-4 mt-0.5 shrink-0" />
|
|
304
294
|
<span>{error}</span>
|
|
305
295
|
</div>
|
|
@@ -464,8 +454,8 @@ export default function SharedSkillDetailPage({
|
|
|
464
454
|
|
|
465
455
|
{playbook && (
|
|
466
456
|
<aside className="space-y-3 lg:sticky lg:top-6 lg:self-start">
|
|
467
|
-
<div className="rounded-
|
|
468
|
-
<h3 className="text-xs font-semibold uppercase
|
|
457
|
+
<div className="rounded-lg border border-border bg-card p-4">
|
|
458
|
+
<h3 className="text-xs font-semibold uppercase text-muted-foreground mb-3">
|
|
469
459
|
Metadata
|
|
470
460
|
</h3>
|
|
471
461
|
<dl className="space-y-2.5 text-sm">
|
|
@@ -545,7 +535,7 @@ function Prose({ text, muted = false }: { text: string; muted?: boolean }) {
|
|
|
545
535
|
return (
|
|
546
536
|
<div
|
|
547
537
|
className={cn(
|
|
548
|
-
"rounded-
|
|
538
|
+
"rounded-lg border border-border bg-card px-4 py-3",
|
|
549
539
|
muted && "bg-muted/30",
|
|
550
540
|
)}
|
|
551
541
|
>
|
|
@@ -573,7 +563,7 @@ function AutoTextarea({
|
|
|
573
563
|
onChange={(e) => onChange(e.target.value)}
|
|
574
564
|
rows={rows}
|
|
575
565
|
placeholder={placeholder}
|
|
576
|
-
className="w-full rounded-
|
|
566
|
+
className="w-full rounded-lg border border-input bg-transparent px-4 py-3 text-sm leading-relaxed font-sans resize-y outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 placeholder:text-muted-foreground"
|
|
577
567
|
/>
|
|
578
568
|
);
|
|
579
569
|
}
|
|
@@ -808,23 +798,16 @@ function DangerZone({
|
|
|
808
798
|
disabled: boolean;
|
|
809
799
|
}) {
|
|
810
800
|
return (
|
|
811
|
-
<
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
onClick={onDelete}
|
|
823
|
-
disabled={deleting || disabled}
|
|
824
|
-
>
|
|
825
|
-
<Trash2 className="h-3.5 w-3.5" />
|
|
826
|
-
{deleting ? "Deleting…" : "Delete"}
|
|
827
|
-
</Button>
|
|
828
|
-
</section>
|
|
801
|
+
<DeleteLearningDangerZone
|
|
802
|
+
learningName="this shared skill"
|
|
803
|
+
description="Delete this shared claude-smart skill permanently."
|
|
804
|
+
consequences={[
|
|
805
|
+
"This shared skill will stop being available to claude-smart.",
|
|
806
|
+
"Historical session records may still show prior uses.",
|
|
807
|
+
]}
|
|
808
|
+
onDelete={onDelete}
|
|
809
|
+
deleting={deleting}
|
|
810
|
+
disabled={disabled}
|
|
811
|
+
/>
|
|
829
812
|
);
|
|
830
813
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useId, useState } from "react";
|
|
4
|
+
import { AlertTriangle, Trash2, X } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
|
|
8
|
+
type DeleteLearningDangerZoneProps = {
|
|
9
|
+
learningName: string;
|
|
10
|
+
description: string;
|
|
11
|
+
consequences: string[];
|
|
12
|
+
deleting: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
onDelete: () => void | Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const REQUIRED_CONFIRMATION = "DELETE";
|
|
18
|
+
|
|
19
|
+
export function DeleteLearningDangerZone({
|
|
20
|
+
learningName,
|
|
21
|
+
description,
|
|
22
|
+
consequences,
|
|
23
|
+
deleting,
|
|
24
|
+
disabled = false,
|
|
25
|
+
onDelete,
|
|
26
|
+
}: DeleteLearningDangerZoneProps) {
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [confirmation, setConfirmation] = useState("");
|
|
29
|
+
const titleId = useId();
|
|
30
|
+
const descriptionId = useId();
|
|
31
|
+
const canDelete = confirmation === REQUIRED_CONFIRMATION && !deleting;
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!open) return;
|
|
35
|
+
|
|
36
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
37
|
+
if (event.key === "Escape" && !deleting) {
|
|
38
|
+
setOpen(false);
|
|
39
|
+
setConfirmation("");
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
window.addEventListener("keydown", onKeyDown);
|
|
44
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
45
|
+
}, [deleting, open]);
|
|
46
|
+
|
|
47
|
+
const close = () => {
|
|
48
|
+
if (deleting) return;
|
|
49
|
+
setOpen(false);
|
|
50
|
+
setConfirmation("");
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const confirmDelete = async () => {
|
|
54
|
+
if (!canDelete) return;
|
|
55
|
+
await onDelete();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<section className="rounded-xl border border-destructive/30 bg-destructive/5 p-4 flex items-start justify-between gap-4">
|
|
61
|
+
<div className="min-w-0">
|
|
62
|
+
<h3 className="text-sm font-semibold text-destructive">
|
|
63
|
+
Danger zone
|
|
64
|
+
</h3>
|
|
65
|
+
<p className="text-xs text-muted-foreground mt-0.5">
|
|
66
|
+
{description}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
<Button
|
|
70
|
+
variant="destructive"
|
|
71
|
+
size="sm"
|
|
72
|
+
onClick={() => setOpen(true)}
|
|
73
|
+
disabled={deleting || disabled}
|
|
74
|
+
>
|
|
75
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
76
|
+
Delete
|
|
77
|
+
</Button>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
{open && (
|
|
81
|
+
<div
|
|
82
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-background/80 p-4 backdrop-blur-sm"
|
|
83
|
+
role="dialog"
|
|
84
|
+
aria-modal="true"
|
|
85
|
+
aria-labelledby={titleId}
|
|
86
|
+
aria-describedby={descriptionId}
|
|
87
|
+
>
|
|
88
|
+
<div className="w-full max-w-md rounded-xl border border-border bg-card shadow-lg">
|
|
89
|
+
<div className="flex items-start justify-between gap-4 border-b border-border px-5 py-4">
|
|
90
|
+
<div className="flex items-start gap-3">
|
|
91
|
+
<div className="mt-0.5 rounded-full bg-destructive/10 p-2 text-destructive">
|
|
92
|
+
<AlertTriangle className="h-4 w-4" />
|
|
93
|
+
</div>
|
|
94
|
+
<div>
|
|
95
|
+
<h2 id={titleId} className="text-base font-semibold">
|
|
96
|
+
Delete learning?
|
|
97
|
+
</h2>
|
|
98
|
+
<p
|
|
99
|
+
id={descriptionId}
|
|
100
|
+
className="mt-1 text-sm text-muted-foreground"
|
|
101
|
+
>
|
|
102
|
+
This permanently deletes {learningName}.
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<Button
|
|
107
|
+
type="button"
|
|
108
|
+
variant="ghost"
|
|
109
|
+
size="sm"
|
|
110
|
+
onClick={close}
|
|
111
|
+
disabled={deleting}
|
|
112
|
+
aria-label="Close"
|
|
113
|
+
>
|
|
114
|
+
<X className="h-4 w-4" />
|
|
115
|
+
</Button>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="space-y-4 px-5 py-4">
|
|
119
|
+
<ul className="list-disc space-y-1.5 pl-5 text-sm text-muted-foreground">
|
|
120
|
+
{consequences.map((item) => (
|
|
121
|
+
<li key={item}>{item}</li>
|
|
122
|
+
))}
|
|
123
|
+
<li>This cannot be undone.</li>
|
|
124
|
+
</ul>
|
|
125
|
+
|
|
126
|
+
<label className="block space-y-2">
|
|
127
|
+
<span className="text-sm font-medium">
|
|
128
|
+
Type DELETE to confirm
|
|
129
|
+
</span>
|
|
130
|
+
<Input
|
|
131
|
+
value={confirmation}
|
|
132
|
+
onChange={(event) => setConfirmation(event.target.value)}
|
|
133
|
+
placeholder="DELETE"
|
|
134
|
+
autoFocus
|
|
135
|
+
disabled={deleting}
|
|
136
|
+
/>
|
|
137
|
+
</label>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="flex items-center justify-end gap-2 border-t border-border px-5 py-4">
|
|
141
|
+
<Button
|
|
142
|
+
type="button"
|
|
143
|
+
variant="outline"
|
|
144
|
+
size="sm"
|
|
145
|
+
onClick={close}
|
|
146
|
+
disabled={deleting}
|
|
147
|
+
>
|
|
148
|
+
Cancel
|
|
149
|
+
</Button>
|
|
150
|
+
<Button
|
|
151
|
+
type="button"
|
|
152
|
+
variant="destructive"
|
|
153
|
+
size="sm"
|
|
154
|
+
onClick={confirmDelete}
|
|
155
|
+
disabled={!canDelete || disabled}
|
|
156
|
+
>
|
|
157
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
158
|
+
{deleting ? "Deleting..." : "Delete learning"}
|
|
159
|
+
</Button>
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -17,12 +17,14 @@ export function EmptyState({
|
|
|
17
17
|
return (
|
|
18
18
|
<div
|
|
19
19
|
className={cn(
|
|
20
|
-
"flex flex-col items-center justify-center text-center py-12 px-6 border border-dashed border-border rounded-
|
|
20
|
+
"flex flex-col items-center justify-center text-center py-12 px-6 border border-dashed border-border rounded-lg bg-card/72",
|
|
21
21
|
className,
|
|
22
22
|
)}
|
|
23
23
|
>
|
|
24
24
|
{Icon && (
|
|
25
|
-
<
|
|
25
|
+
<div className="mb-3 flex h-12 w-12 items-center justify-center rounded-lg border border-primary/15 bg-primary/10 text-primary">
|
|
26
|
+
<Icon className="h-6 w-6" strokeWidth={1.5} />
|
|
27
|
+
</div>
|
|
26
28
|
)}
|
|
27
29
|
<div className="text-sm font-medium text-foreground">{title}</div>
|
|
28
30
|
{description && (
|
|
@@ -17,7 +17,7 @@ export function LearningsBadge({
|
|
|
17
17
|
<Badge
|
|
18
18
|
variant="outline"
|
|
19
19
|
className={cn(
|
|
20
|
-
"border-amber-500/
|
|
20
|
+
"border-amber-500/45 bg-amber-500/10 text-amber-700 gap-1 dark:text-amber-300",
|
|
21
21
|
isSmall ? "h-4 px-1.5 text-[10px] shrink-0" : "h-5",
|
|
22
22
|
className,
|
|
23
23
|
)}
|
|
@@ -14,14 +14,16 @@ export function PageHeader({
|
|
|
14
14
|
return (
|
|
15
15
|
<div
|
|
16
16
|
className={cn(
|
|
17
|
-
"border-b border-border px-6 py-
|
|
17
|
+
"border-b border-border bg-background/72 px-6 py-6 flex flex-wrap items-start justify-between gap-4 backdrop-blur",
|
|
18
18
|
className,
|
|
19
19
|
)}
|
|
20
20
|
>
|
|
21
21
|
<div className="min-w-[min(18rem,100%)] flex-1">
|
|
22
|
-
<h1 className="text-
|
|
22
|
+
<h1 className="text-2xl font-semibold">{title}</h1>
|
|
23
23
|
{description && (
|
|
24
|
-
<p className="text-sm text-muted-foreground mt-
|
|
24
|
+
<p className="text-sm text-muted-foreground mt-1 max-w-3xl">
|
|
25
|
+
{description}
|
|
26
|
+
</p>
|
|
25
27
|
)}
|
|
26
28
|
</div>
|
|
27
29
|
{actions && (
|
|
@@ -75,11 +75,11 @@ export function PageTabs({
|
|
|
75
75
|
);
|
|
76
76
|
|
|
77
77
|
const className = cn(
|
|
78
|
-
"relative rounded-lg border px-
|
|
79
|
-
"hover:border-
|
|
78
|
+
"relative rounded-lg border px-4 py-3 text-left transition-colors",
|
|
79
|
+
"hover:border-primary/35 hover:bg-accent/45 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
|
|
80
80
|
active
|
|
81
|
-
? "border-
|
|
82
|
-
: "border-border bg-background/
|
|
81
|
+
? "border-primary/35 bg-card text-foreground shadow-sm before:absolute before:inset-x-4 before:top-0 before:h-0.5 before:rounded-full before:bg-primary"
|
|
82
|
+
: "border-border bg-background/70 text-muted-foreground",
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
if (item.href) {
|
|
@@ -17,22 +17,26 @@ export function StatCard({
|
|
|
17
17
|
return (
|
|
18
18
|
<div
|
|
19
19
|
className={cn(
|
|
20
|
-
"rounded-
|
|
20
|
+
"rounded-lg border border-border bg-card/92 px-5 py-4 flex items-start justify-between gap-4 shadow-sm",
|
|
21
21
|
className,
|
|
22
22
|
)}
|
|
23
23
|
>
|
|
24
24
|
<div className="min-w-0">
|
|
25
|
-
<div className="text-xs uppercase
|
|
25
|
+
<div className="text-xs uppercase text-muted-foreground font-semibold">
|
|
26
26
|
{label}
|
|
27
27
|
</div>
|
|
28
|
-
<div className="mt-
|
|
28
|
+
<div className="mt-2 text-3xl font-semibold tabular-nums text-foreground">
|
|
29
29
|
{value}
|
|
30
30
|
</div>
|
|
31
31
|
{hint && (
|
|
32
|
-
<div className="text-xs text-muted-foreground mt-1">{hint}</div>
|
|
32
|
+
<div className="text-xs text-muted-foreground mt-1.5">{hint}</div>
|
|
33
33
|
)}
|
|
34
34
|
</div>
|
|
35
|
-
{Icon &&
|
|
35
|
+
{Icon && (
|
|
36
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border border-primary/15 bg-primary/10 text-primary">
|
|
37
|
+
<Icon className="h-4 w-4" />
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
36
40
|
</div>
|
|
37
41
|
);
|
|
38
42
|
}
|
|
@@ -11,13 +11,20 @@ export function Sidebar() {
|
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<ScrollArea className="h-full">
|
|
14
|
-
<div className="px-3 py-
|
|
15
|
-
<div className="
|
|
16
|
-
<div className="
|
|
17
|
-
|
|
14
|
+
<div className="px-3 py-5">
|
|
15
|
+
<div className="mb-6 rounded-lg border border-sidebar-border bg-background/55 p-3">
|
|
16
|
+
<div className="flex items-center gap-2">
|
|
17
|
+
<div className="h-2.5 w-2.5 rounded-full bg-primary" />
|
|
18
|
+
<div className="font-semibold text-sm text-sidebar-foreground">
|
|
19
|
+
Claude-Smart
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div className="mt-1 text-[11px] text-muted-foreground">
|
|
23
|
+
Sessions, preferences, skills, and runtime controls.
|
|
24
|
+
</div>
|
|
18
25
|
</div>
|
|
19
26
|
|
|
20
|
-
<nav className="space-y-
|
|
27
|
+
<nav className="space-y-1">
|
|
21
28
|
{navItems.map((item) => {
|
|
22
29
|
const active =
|
|
23
30
|
pathname === item.href || pathname.startsWith(`${item.href}/`);
|
|
@@ -27,13 +34,21 @@ export function Sidebar() {
|
|
|
27
34
|
key={item.href}
|
|
28
35
|
href={item.href}
|
|
29
36
|
className={cn(
|
|
30
|
-
"flex items-center gap-2 px-2 py-
|
|
37
|
+
"group relative flex items-center gap-2 rounded-lg px-2.5 py-2 text-sm transition-colors",
|
|
31
38
|
active
|
|
32
|
-
? "bg-accent text-accent-foreground font-medium"
|
|
33
|
-
: "text-muted-foreground hover:
|
|
39
|
+
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium shadow-sm"
|
|
40
|
+
: "text-muted-foreground hover:bg-background/70 hover:text-foreground",
|
|
34
41
|
)}
|
|
35
42
|
>
|
|
36
|
-
|
|
43
|
+
{active && (
|
|
44
|
+
<span className="absolute left-0 top-1/2 h-5 w-0.5 -translate-y-1/2 rounded-full bg-primary" />
|
|
45
|
+
)}
|
|
46
|
+
<Icon
|
|
47
|
+
className={cn(
|
|
48
|
+
"h-4 w-4 shrink-0",
|
|
49
|
+
active ? "text-primary" : "text-muted-foreground",
|
|
50
|
+
)}
|
|
51
|
+
/>
|
|
37
52
|
<span className="truncate">{item.label}</span>
|
|
38
53
|
</Link>
|
|
39
54
|
);
|