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.
Files changed (64) hide show
  1. package/README.md +2 -2
  2. package/bin/claude-smart.js +172 -18
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/.codex-plugin/plugin.json +1 -1
  6. package/plugin/dashboard/app/api/config/route.ts +11 -2
  7. package/plugin/dashboard/app/api/health/route.ts +45 -6
  8. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +15 -7
  9. package/plugin/dashboard/app/api/rules/applied/route.ts +27 -0
  10. package/plugin/dashboard/app/configure/env/page.tsx +36 -31
  11. package/plugin/dashboard/app/configure/layout.tsx +1 -1
  12. package/plugin/dashboard/app/configure/server/page.tsx +8 -14
  13. package/plugin/dashboard/app/dashboard/page.tsx +311 -115
  14. package/plugin/dashboard/app/globals.css +80 -66
  15. package/plugin/dashboard/app/layout.tsx +13 -10
  16. package/plugin/dashboard/app/preferences/[id]/page.tsx +21 -32
  17. package/plugin/dashboard/app/preferences/page.tsx +154 -54
  18. package/plugin/dashboard/app/preferences/project/[id]/page.tsx +1 -0
  19. package/plugin/dashboard/app/rules/[id]/page.tsx +51 -0
  20. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +4 -4
  21. package/plugin/dashboard/app/sessions/page.tsx +14 -10
  22. package/plugin/dashboard/app/skills/page.tsx +175 -56
  23. package/plugin/dashboard/app/skills/project/[id]/page.tsx +22 -38
  24. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +20 -37
  25. package/plugin/dashboard/components/common/delete-learning-danger-zone.tsx +166 -0
  26. package/plugin/dashboard/components/common/empty-state.tsx +4 -2
  27. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  28. package/plugin/dashboard/components/common/page-header.tsx +5 -3
  29. package/plugin/dashboard/components/common/page-tabs.tsx +4 -4
  30. package/plugin/dashboard/components/common/stat-card.tsx +9 -5
  31. package/plugin/dashboard/components/layout/sidebar.tsx +24 -9
  32. package/plugin/dashboard/components/layout/top-bar.tsx +37 -25
  33. package/plugin/dashboard/components/ui/input.tsx +1 -0
  34. package/plugin/dashboard/hooks/use-settings.tsx +30 -61
  35. package/plugin/dashboard/hooks/use-stall-state.ts +5 -9
  36. package/plugin/dashboard/lib/config-file.ts +2 -0
  37. package/plugin/dashboard/lib/reflexio-client.ts +23 -48
  38. package/plugin/dashboard/lib/session-reader.ts +222 -6
  39. package/plugin/dashboard/lib/types.ts +20 -1
  40. package/plugin/dashboard/package-lock.json +70 -95
  41. package/plugin/dashboard/package.json +5 -2
  42. package/plugin/hooks/hooks.json +1 -1
  43. package/plugin/pyproject.toml +1 -1
  44. package/plugin/scripts/_lib.sh +126 -0
  45. package/plugin/scripts/backend-service.sh +32 -7
  46. package/plugin/scripts/cli.sh +4 -2
  47. package/plugin/scripts/codex-hook.js +100 -3
  48. package/plugin/scripts/dashboard-service.sh +98 -19
  49. package/plugin/scripts/hook_entry.sh +32 -11
  50. package/plugin/scripts/smart-install.sh +27 -44
  51. package/plugin/src/claude_smart/cli.py +204 -20
  52. package/plugin/src/claude_smart/context_format.py +244 -6
  53. package/plugin/src/claude_smart/context_inject.py +8 -1
  54. package/plugin/src/claude_smart/cs_cite.py +186 -34
  55. package/plugin/src/claude_smart/env_config.py +102 -0
  56. package/plugin/src/claude_smart/events/session_end.py +171 -6
  57. package/plugin/src/claude_smart/events/session_start.py +26 -2
  58. package/plugin/src/claude_smart/events/stop.py +48 -9
  59. package/plugin/src/claude_smart/hook.py +62 -4
  60. package/plugin/src/claude_smart/hook_log.py +301 -0
  61. package/plugin/src/claude_smart/internal_call.py +30 -0
  62. package/plugin/src/claude_smart/publish.py +5 -0
  63. package/plugin/src/claude_smart/reflexio_adapter.py +102 -26
  64. 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({ reflexioUrl })
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, reflexioUrl]);
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, reflexioUrl);
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-xl border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm flex items-start gap-2">
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-xl border border-border bg-card p-4">
327
- <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-3">
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-xl border border-border bg-card p-4">
369
- <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-3">
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-xl border border-border bg-card px-4 py-3",
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-xl 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"
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
- <section className="rounded-xl border border-destructive/30 bg-destructive/5 p-4 flex items-start justify-between gap-4">
579
- <div className="min-w-0">
580
- <h3 className="text-sm font-semibold text-destructive">Danger zone</h3>
581
- <p className="text-xs text-muted-foreground mt-0.5">
582
- Deleting removes this project-specific skill permanently. It will stop being
583
- injected into future sessions.
584
- </p>
585
- </div>
586
- <Button
587
- variant="destructive"
588
- size="sm"
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({ reflexioUrl })
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, reflexioUrl]);
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, reflexioUrl);
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-xl border border-destructive/30 bg-destructive/5 text-destructive px-4 py-3 text-sm flex items-start gap-2">
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-xl border border-border bg-card p-4">
468
- <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-3">
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-xl border border-border bg-card px-4 py-3",
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-xl 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"
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
- <section className="rounded-xl border border-destructive/30 bg-destructive/5 p-4 flex items-start justify-between gap-4">
812
- <div className="min-w-0">
813
- <h3 className="text-sm font-semibold text-destructive">Danger zone</h3>
814
- <p className="text-xs text-muted-foreground mt-0.5">
815
- Deleting removes this shared skill permanently. It will stop being
816
- available in the dashboard.
817
- </p>
818
- </div>
819
- <Button
820
- variant="destructive"
821
- size="sm"
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-xl bg-muted/20",
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
- <Icon className="h-8 w-8 text-muted-foreground/60 mb-3" strokeWidth={1.5} />
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/40 gap-1",
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-5 flex flex-wrap items-start justify-between gap-4",
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-xl font-semibold tracking-tight">{title}</h1>
22
+ <h1 className="text-2xl font-semibold">{title}</h1>
23
23
  {description && (
24
- <p className="text-sm text-muted-foreground mt-0.5">{description}</p>
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-3 py-2.5 text-left transition-colors",
79
- "hover:border-foreground/25 hover:bg-accent/40 focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50",
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-foreground/25 bg-card text-foreground shadow-sm before:absolute before:inset-x-3 before:top-0 before:h-0.5 before:rounded-full before:bg-foreground"
82
- : "border-border bg-background/60 text-muted-foreground",
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-xl border border-border bg-card px-5 py-4 flex items-start justify-between gap-4",
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 tracking-wide text-muted-foreground font-medium">
25
+ <div className="text-xs uppercase text-muted-foreground font-semibold">
26
26
  {label}
27
27
  </div>
28
- <div className="mt-1.5 text-2xl font-semibold tracking-tight tabular-nums">
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 && <Icon className="h-4 w-4 text-muted-foreground mt-0.5" />}
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-4">
15
- <div className="px-2 mb-6">
16
- <div className="font-semibold text-sm">Claude-Smart</div>
17
- <div className="text-[11px] text-muted-foreground font-mono">dashboard</div>
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-0.5">
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-1.5 text-sm rounded-md transition-colors",
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:text-foreground hover:bg-accent/50",
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
- <Icon className="h-4 w-4 shrink-0" />
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
  );