selftune 0.2.2 → 0.2.6

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 (53) hide show
  1. package/README.md +11 -0
  2. package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +1 -0
  3. package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +15 -0
  4. package/apps/local-dashboard/dist/assets/vendor-ui-r2k_Ku_V.js +346 -0
  5. package/apps/local-dashboard/dist/index.html +3 -3
  6. package/cli/selftune/analytics.ts +354 -0
  7. package/cli/selftune/badge/badge.ts +2 -2
  8. package/cli/selftune/dashboard-server.ts +3 -3
  9. package/cli/selftune/evolution/evolve-body.ts +1 -1
  10. package/cli/selftune/evolution/evolve.ts +1 -1
  11. package/cli/selftune/index.ts +15 -1
  12. package/cli/selftune/init.ts +5 -1
  13. package/cli/selftune/observability.ts +63 -2
  14. package/cli/selftune/orchestrate.ts +1 -1
  15. package/cli/selftune/quickstart.ts +1 -1
  16. package/cli/selftune/status.ts +2 -2
  17. package/cli/selftune/types.ts +1 -0
  18. package/cli/selftune/utils/llm-call.ts +2 -1
  19. package/package.json +6 -4
  20. package/packages/ui/README.md +113 -0
  21. package/packages/ui/index.ts +10 -0
  22. package/packages/ui/package.json +62 -0
  23. package/packages/ui/src/components/ActivityTimeline.tsx +171 -0
  24. package/packages/ui/src/components/EvidenceViewer.tsx +718 -0
  25. package/packages/ui/src/components/EvolutionTimeline.tsx +252 -0
  26. package/packages/ui/src/components/InfoTip.tsx +19 -0
  27. package/packages/ui/src/components/OrchestrateRunsPanel.tsx +164 -0
  28. package/packages/ui/src/components/index.ts +7 -0
  29. package/packages/ui/src/components/section-cards.tsx +155 -0
  30. package/packages/ui/src/components/skill-health-grid.tsx +686 -0
  31. package/packages/ui/src/lib/constants.tsx +43 -0
  32. package/packages/ui/src/lib/format.ts +37 -0
  33. package/packages/ui/src/lib/index.ts +3 -0
  34. package/packages/ui/src/lib/utils.ts +6 -0
  35. package/packages/ui/src/primitives/badge.tsx +52 -0
  36. package/packages/ui/src/primitives/button.tsx +58 -0
  37. package/packages/ui/src/primitives/card.tsx +103 -0
  38. package/packages/ui/src/primitives/checkbox.tsx +27 -0
  39. package/packages/ui/src/primitives/collapsible.tsx +7 -0
  40. package/packages/ui/src/primitives/dropdown-menu.tsx +266 -0
  41. package/packages/ui/src/primitives/index.ts +55 -0
  42. package/packages/ui/src/primitives/label.tsx +20 -0
  43. package/packages/ui/src/primitives/select.tsx +197 -0
  44. package/packages/ui/src/primitives/table.tsx +114 -0
  45. package/packages/ui/src/primitives/tabs.tsx +82 -0
  46. package/packages/ui/src/primitives/tooltip.tsx +64 -0
  47. package/packages/ui/src/types.ts +87 -0
  48. package/packages/ui/tsconfig.json +17 -0
  49. package/skill/SKILL.md +3 -0
  50. package/skill/Workflows/Telemetry.md +59 -0
  51. package/apps/local-dashboard/dist/assets/index-C4EOTFZ2.js +0 -15
  52. package/apps/local-dashboard/dist/assets/index-bl-Webyd.css +0 -1
  53. package/apps/local-dashboard/dist/assets/vendor-ui-D7_zX_qy.js +0 -346
@@ -0,0 +1,113 @@
1
+ # @selftune/ui
2
+
3
+ Shared UI components for selftune dashboards. Source-only workspace package — no build step, consumed directly by Vite's bundler.
4
+
5
+ ## Usage
6
+
7
+ Add as a workspace dependency:
8
+
9
+ ```json
10
+ {
11
+ "dependencies": {
12
+ "@selftune/ui": "workspace:*"
13
+ }
14
+ }
15
+ ```
16
+
17
+ Import from subpath exports:
18
+
19
+ ```tsx
20
+ import { Badge, Button, Card } from "@selftune/ui/primitives"
21
+ import { SkillHealthGrid, EvolutionTimeline } from "@selftune/ui/components"
22
+ import { cn, timeAgo, deriveStatus, STATUS_CONFIG } from "@selftune/ui/lib"
23
+ import type { SkillCard, EvolutionEntry } from "@selftune/ui/types"
24
+ ```
25
+
26
+ Or import everything from the root:
27
+
28
+ ```tsx
29
+ import { Badge, SkillHealthGrid, cn, type SkillCard } from "@selftune/ui"
30
+ ```
31
+
32
+ ## Exports
33
+
34
+ ### Primitives (`@selftune/ui/primitives`)
35
+
36
+ shadcn/ui components built on [@base-ui/react](https://base-ui.com/):
37
+
38
+ | Component | Source |
39
+ |-----------|--------|
40
+ | `Badge`, `badgeVariants` | badge.tsx |
41
+ | `Button`, `buttonVariants` | button.tsx |
42
+ | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` | card.tsx |
43
+ | `Checkbox` | checkbox.tsx |
44
+ | `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | collapsible.tsx |
45
+ | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, ... | dropdown-menu.tsx |
46
+ | `Label` | label.tsx |
47
+ | `Select`, `SelectTrigger`, `SelectContent`, `SelectItem`, ... | select.tsx |
48
+ | `Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, ... | table.tsx |
49
+ | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | tabs.tsx |
50
+ | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider` | tooltip.tsx |
51
+
52
+ ### Domain Components (`@selftune/ui/components`)
53
+
54
+ Presentational components for selftune dashboard views. No data fetching, no routing — pass data and callbacks as props.
55
+
56
+ | Component | Description |
57
+ |-----------|-------------|
58
+ | `SkillHealthGrid` | Sortable/filterable data table with drag-and-drop, pagination, and view tabs. Accepts `renderSkillName` prop for custom routing. |
59
+ | `EvolutionTimeline` | Proposal lifecycle timeline grouped by proposal ID, with pass rate deltas. |
60
+ | `ActivityPanel` | Tabbed activity feed (pending proposals, timeline events, unmatched queries). |
61
+ | `EvidenceViewer` | Full evidence trail for a proposal — side-by-side diffs, validation results, iteration rounds. |
62
+ | `SectionCards` | Dashboard metric stat cards (skills count, pass rate, unmatched, sessions, etc.). |
63
+ | `OrchestrateRunsPanel` | Collapsible orchestrate run reports with per-skill action details. |
64
+ | `InfoTip` | Small info icon with tooltip, used to explain metrics. |
65
+
66
+ ### Utilities (`@selftune/ui/lib`)
67
+
68
+ | Export | Description |
69
+ |--------|-------------|
70
+ | `cn(...inputs)` | Tailwind class merge utility (clsx + tailwind-merge) |
71
+ | `timeAgo(timestamp)` | Relative time string ("3h ago", "2d ago") |
72
+ | `formatRate(rate)` | Format 0-1 rate as percentage string ("85%") |
73
+ | `deriveStatus(passRate, checks)` | Derive `SkillHealthStatus` from pass rate and check count |
74
+ | `sortByPassRateAndChecks(items)` | Sort skill cards by pass rate ascending, then checks descending |
75
+ | `STATUS_CONFIG` | Icon, variant, and label for each `SkillHealthStatus` value |
76
+
77
+ ### Types (`@selftune/ui/types`)
78
+
79
+ Self-contained type declarations matching the dashboard contract shapes:
80
+
81
+ `SkillCard`, `SkillHealthStatus`, `EvalSnapshot`, `EvolutionEntry`, `EvidenceEntry`, `PendingProposal`, `UnmatchedQuery`, `OrchestrateRunReport`, `OrchestrateRunSkillAction`
82
+
83
+ ## Tailwind CSS
84
+
85
+ This package uses Tailwind v4. The Vite plugin auto-scans imported workspace packages, so classes should be detected automatically. If not, add to your app's `styles.css`:
86
+
87
+ ```css
88
+ @source "../../packages/ui/src";
89
+ ```
90
+
91
+ ## Adding a New Primitive
92
+
93
+ 1. Copy the shadcn component into `src/primitives/`
94
+ 2. Replace `@/lib/utils` with `../lib/utils` in the import
95
+ 3. Re-export from `src/primitives/index.ts`
96
+
97
+ ## Adding a New Domain Component
98
+
99
+ 1. Create the component in `src/components/`
100
+ 2. Import primitives from `../primitives/`, utils from `../lib/`, types from `../types`
101
+ 3. Keep it **purely presentational** — no data fetching, no router imports
102
+ 4. For navigation, accept a render prop (e.g., `renderSkillName`) instead of importing router components
103
+ 5. Re-export from `src/components/index.ts`
104
+
105
+ ## Peer Dependencies
106
+
107
+ Required: `react`, `react-dom`
108
+
109
+ Optional (only needed by specific components):
110
+ - `@dnd-kit/*` — SkillHealthGrid drag-and-drop
111
+ - `@tanstack/react-table` — SkillHealthGrid table
112
+ - `react-markdown` — EvidenceViewer markdown rendering
113
+ - `recharts` — future chart components
@@ -0,0 +1,10 @@
1
+ // Primitives
2
+
3
+ // Domain components
4
+ export * from "./src/components/index";
5
+ // Utilities
6
+ export * from "./src/lib/index";
7
+ export * from "./src/primitives/index";
8
+
9
+ // Types
10
+ export * from "./src/types";
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@selftune/ui",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Shared UI components for selftune dashboards",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "Daniel Petro",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/selftune-dev/selftune.git",
12
+ "directory": "packages/ui"
13
+ },
14
+ "exports": {
15
+ ".": "./index.ts",
16
+ "./primitives": "./src/primitives/index.ts",
17
+ "./components": "./src/components/index.ts",
18
+ "./lib": "./src/lib/index.ts",
19
+ "./types": "./src/types.ts"
20
+ },
21
+ "dependencies": {
22
+ "@base-ui/react": "^1.3.0",
23
+ "class-variance-authority": "^0.7.1",
24
+ "clsx": "^2.1.1",
25
+ "lucide-react": "^0.577.0",
26
+ "tailwind-merge": "^3.5.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^19.0.0",
30
+ "react-dom": "^19.0.0",
31
+ "@dnd-kit/core": "^6.0.0",
32
+ "@dnd-kit/modifiers": "^9.0.0",
33
+ "@dnd-kit/sortable": "^10.0.0",
34
+ "@dnd-kit/utilities": "^3.0.0",
35
+ "@tanstack/react-table": "^8.0.0",
36
+ "react-markdown": "^10.0.0",
37
+ "recharts": "^2.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "@dnd-kit/core": {
41
+ "optional": true
42
+ },
43
+ "@dnd-kit/modifiers": {
44
+ "optional": true
45
+ },
46
+ "@dnd-kit/sortable": {
47
+ "optional": true
48
+ },
49
+ "@dnd-kit/utilities": {
50
+ "optional": true
51
+ },
52
+ "@tanstack/react-table": {
53
+ "optional": true
54
+ },
55
+ "react-markdown": {
56
+ "optional": true
57
+ },
58
+ "recharts": {
59
+ "optional": true
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,171 @@
1
+ import { Badge } from "../primitives/badge"
2
+ import {
3
+ Card,
4
+ CardContent,
5
+ CardDescription,
6
+ CardHeader,
7
+ CardTitle,
8
+ } from "../primitives/card"
9
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../primitives/tabs"
10
+ import type { EvolutionEntry, PendingProposal, UnmatchedQuery } from "../types"
11
+ import { timeAgo } from "../lib/format"
12
+ import {
13
+ ClockIcon,
14
+ GitPullRequestArrowIcon,
15
+ SearchXIcon,
16
+ ActivityIcon,
17
+ } from "lucide-react"
18
+
19
+ const ACTION_VARIANT: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
20
+ created: "outline",
21
+ validated: "secondary",
22
+ deployed: "default",
23
+ rejected: "destructive",
24
+ rolled_back: "destructive",
25
+ pending: "secondary",
26
+ }
27
+
28
+ export function ActivityPanel({
29
+ evolution,
30
+ pendingProposals,
31
+ unmatchedQueries,
32
+ }: {
33
+ evolution: EvolutionEntry[]
34
+ pendingProposals: PendingProposal[]
35
+ unmatchedQueries: UnmatchedQuery[]
36
+ }) {
37
+ const hasActivity = evolution.length > 0 || pendingProposals.length > 0 || unmatchedQueries.length > 0
38
+
39
+ if (!hasActivity) {
40
+ return (
41
+ <Card>
42
+ <CardHeader>
43
+ <CardTitle className="flex items-center gap-2 text-sm">
44
+ <ActivityIcon className="size-4" />
45
+ Activity
46
+ </CardTitle>
47
+ </CardHeader>
48
+ <CardContent>
49
+ <p className="text-sm text-muted-foreground text-center py-8">
50
+ No recent activity
51
+ </p>
52
+ </CardContent>
53
+ </Card>
54
+ )
55
+ }
56
+
57
+ return (
58
+ <Card>
59
+ <CardHeader>
60
+ <CardTitle className="flex items-center gap-2 text-sm">
61
+ <ActivityIcon className="size-4" />
62
+ Activity
63
+ </CardTitle>
64
+ <CardDescription>Recent evolution events and queries</CardDescription>
65
+ </CardHeader>
66
+ <CardContent>
67
+ <Tabs
68
+ defaultValue={
69
+ pendingProposals.length > 0
70
+ ? "pending"
71
+ : evolution.length > 0
72
+ ? "timeline"
73
+ : "unmatched"
74
+ }
75
+ >
76
+ <TabsList className="w-full">
77
+ {pendingProposals.length > 0 && (
78
+ <TabsTrigger value="pending" className="flex-1 gap-1.5">
79
+ <GitPullRequestArrowIcon className="size-3.5" />
80
+ Pending
81
+ <Badge variant="secondary" className="ml-1 h-4 px-1.5 text-[10px]">
82
+ {pendingProposals.length}
83
+ </Badge>
84
+ </TabsTrigger>
85
+ )}
86
+ <TabsTrigger value="timeline" className="flex-1 gap-1.5">
87
+ <ClockIcon className="size-3.5" />
88
+ Timeline
89
+ </TabsTrigger>
90
+ {unmatchedQueries.length > 0 && (
91
+ <TabsTrigger value="unmatched" className="flex-1 gap-1.5">
92
+ <SearchXIcon className="size-3.5" />
93
+ Unmatched
94
+ <Badge variant="destructive" className="ml-1 h-4 px-1.5 text-[10px]">
95
+ {unmatchedQueries.length}
96
+ </Badge>
97
+ </TabsTrigger>
98
+ )}
99
+ </TabsList>
100
+
101
+ {pendingProposals.length > 0 && (
102
+ <TabsContent value="pending" className="mt-4 space-y-3">
103
+ {pendingProposals.slice(0, 10).map((p) => (
104
+ <div key={p.proposal_id} className="flex gap-3">
105
+ <div className="mt-1 size-2 shrink-0 rounded-full bg-amber-400" />
106
+ <div className="flex-1 min-w-0 space-y-1">
107
+ <div className="flex items-center gap-2">
108
+ <Badge variant={ACTION_VARIANT[p.action] ?? "secondary"} className="text-[10px]">
109
+ {p.action}
110
+ </Badge>
111
+ <span className="text-xs text-muted-foreground font-mono">
112
+ {timeAgo(p.timestamp)}
113
+ </span>
114
+ </div>
115
+ <p className="text-xs text-muted-foreground line-clamp-2">{p.details}</p>
116
+ </div>
117
+ </div>
118
+ ))}
119
+ </TabsContent>
120
+ )}
121
+
122
+ <TabsContent value="timeline" className="mt-4 space-y-3">
123
+ {evolution.slice(0, 30).map((entry, i) => (
124
+ <div key={`${entry.proposal_id}-${i}`} className="flex gap-3">
125
+ <div className={`mt-1 size-2 shrink-0 rounded-full ${
126
+ entry.action === "deployed" ? "bg-emerald-500"
127
+ : entry.action === "rejected" || entry.action === "rolled_back" ? "bg-red-500"
128
+ : entry.action === "validated" ? "bg-amber-400"
129
+ : "bg-primary-accent"
130
+ }`} />
131
+ <div className="flex-1 min-w-0 space-y-1">
132
+ <div className="flex items-center gap-2">
133
+ <Badge variant={ACTION_VARIANT[entry.action] ?? "secondary"} className="text-[10px]">
134
+ {entry.action}
135
+ </Badge>
136
+ <span className="text-xs text-muted-foreground font-mono">
137
+ {timeAgo(entry.timestamp)}
138
+ </span>
139
+ </div>
140
+ <p className="text-xs text-muted-foreground line-clamp-2">{entry.details}</p>
141
+ <span className="text-[10px] text-muted-foreground/60 font-mono">
142
+ #{entry.proposal_id.slice(0, 8)}
143
+ </span>
144
+ </div>
145
+ </div>
146
+ ))}
147
+ {evolution.length === 0 && (
148
+ <p className="text-sm text-muted-foreground text-center py-4">No timeline events</p>
149
+ )}
150
+ </TabsContent>
151
+
152
+ {unmatchedQueries.length > 0 && (
153
+ <TabsContent value="unmatched" className="mt-4 space-y-2">
154
+ {unmatchedQueries.slice(0, 15).map((q, i) => (
155
+ <div key={`${q.session_id}-${i}`} className="flex gap-3">
156
+ <div className="mt-1 size-2 shrink-0 rounded-full bg-muted-foreground/40" />
157
+ <div className="flex-1 min-w-0 space-y-0.5">
158
+ <span className="text-xs text-muted-foreground font-mono">
159
+ {timeAgo(q.timestamp)}
160
+ </span>
161
+ <p className="text-xs font-mono text-foreground/80 line-clamp-2">{q.query}</p>
162
+ </div>
163
+ </div>
164
+ ))}
165
+ </TabsContent>
166
+ )}
167
+ </Tabs>
168
+ </CardContent>
169
+ </Card>
170
+ )
171
+ }