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.
- package/README.md +11 -0
- package/apps/local-dashboard/dist/assets/index-C75H1Q3n.css +1 -0
- package/apps/local-dashboard/dist/assets/index-axE4kz3Q.js +15 -0
- package/apps/local-dashboard/dist/assets/vendor-ui-r2k_Ku_V.js +346 -0
- package/apps/local-dashboard/dist/index.html +3 -3
- package/cli/selftune/analytics.ts +354 -0
- package/cli/selftune/badge/badge.ts +2 -2
- package/cli/selftune/dashboard-server.ts +3 -3
- package/cli/selftune/evolution/evolve-body.ts +1 -1
- package/cli/selftune/evolution/evolve.ts +1 -1
- package/cli/selftune/index.ts +15 -1
- package/cli/selftune/init.ts +5 -1
- package/cli/selftune/observability.ts +63 -2
- package/cli/selftune/orchestrate.ts +1 -1
- package/cli/selftune/quickstart.ts +1 -1
- package/cli/selftune/status.ts +2 -2
- package/cli/selftune/types.ts +1 -0
- package/cli/selftune/utils/llm-call.ts +2 -1
- package/package.json +6 -4
- package/packages/ui/README.md +113 -0
- package/packages/ui/index.ts +10 -0
- package/packages/ui/package.json +62 -0
- package/packages/ui/src/components/ActivityTimeline.tsx +171 -0
- package/packages/ui/src/components/EvidenceViewer.tsx +718 -0
- package/packages/ui/src/components/EvolutionTimeline.tsx +252 -0
- package/packages/ui/src/components/InfoTip.tsx +19 -0
- package/packages/ui/src/components/OrchestrateRunsPanel.tsx +164 -0
- package/packages/ui/src/components/index.ts +7 -0
- package/packages/ui/src/components/section-cards.tsx +155 -0
- package/packages/ui/src/components/skill-health-grid.tsx +686 -0
- package/packages/ui/src/lib/constants.tsx +43 -0
- package/packages/ui/src/lib/format.ts +37 -0
- package/packages/ui/src/lib/index.ts +3 -0
- package/packages/ui/src/lib/utils.ts +6 -0
- package/packages/ui/src/primitives/badge.tsx +52 -0
- package/packages/ui/src/primitives/button.tsx +58 -0
- package/packages/ui/src/primitives/card.tsx +103 -0
- package/packages/ui/src/primitives/checkbox.tsx +27 -0
- package/packages/ui/src/primitives/collapsible.tsx +7 -0
- package/packages/ui/src/primitives/dropdown-menu.tsx +266 -0
- package/packages/ui/src/primitives/index.ts +55 -0
- package/packages/ui/src/primitives/label.tsx +20 -0
- package/packages/ui/src/primitives/select.tsx +197 -0
- package/packages/ui/src/primitives/table.tsx +114 -0
- package/packages/ui/src/primitives/tabs.tsx +82 -0
- package/packages/ui/src/primitives/tooltip.tsx +64 -0
- package/packages/ui/src/types.ts +87 -0
- package/packages/ui/tsconfig.json +17 -0
- package/skill/SKILL.md +3 -0
- package/skill/Workflows/Telemetry.md +59 -0
- package/apps/local-dashboard/dist/assets/index-C4EOTFZ2.js +0 -15
- package/apps/local-dashboard/dist/assets/index-bl-Webyd.css +0 -1
- 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,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
|
+
}
|