helixevo 0.2.39 → 0.2.40

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.
@@ -20,170 +20,128 @@ interface SkillNodeData {
20
20
 
21
21
  const CATEGORY_STYLES = {
22
22
  generalized: {
23
- border: '#c4b5fd',
24
- bg: 'linear-gradient(180deg, #faf5ff 0%, #f5f0ff 100%)',
25
- headerBg: '#ede9fe',
26
- accent: '#7c3aed',
23
+ border: '#c7b8ff',
24
+ accent: '#6b49df',
27
25
  label: 'GENERALIZED',
26
+ bg: 'linear-gradient(180deg, rgba(250,245,255,0.98) 0%, rgba(244,238,255,0.95) 100%)',
27
+ header: 'linear-gradient(180deg, rgba(237,230,255,0.98), rgba(247,242,255,0.88))',
28
28
  },
29
29
  evolved: {
30
- border: '#86efac',
31
- bg: 'linear-gradient(180deg, #f0fdf4 0%, #ecfdf5 100%)',
32
- headerBg: '#dcfce7',
33
- accent: '#16a34a',
30
+ border: '#b4dfd2',
31
+ accent: '#0f8363',
34
32
  label: 'EVOLVED',
33
+ bg: 'linear-gradient(180deg, rgba(241,252,247,0.98) 0%, rgba(236,249,243,0.95) 100%)',
34
+ header: 'linear-gradient(180deg, rgba(223,245,236,0.98), rgba(243,252,248,0.9))',
35
35
  },
36
36
  original: {
37
- border: '#d1d5db',
38
- bg: 'linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%)',
39
- headerBg: '#f3f4f6',
40
- accent: '#6b7280',
37
+ border: '#ddd6cd',
38
+ accent: '#676259',
41
39
  label: 'ORIGINAL',
40
+ bg: 'linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(249,246,241,0.95) 100%)',
41
+ header: 'linear-gradient(180deg, rgba(244,240,235,0.96), rgba(255,255,255,0.92))',
42
42
  },
43
43
  project: {
44
- border: '#fcd34d',
45
- bg: 'linear-gradient(180deg, #fffbeb 0%, #fef3c7 100%)',
46
- headerBg: '#fef3c7',
47
- accent: '#b45309',
44
+ border: '#ecd28f',
45
+ accent: '#b67b12',
48
46
  label: 'PROJECT',
47
+ bg: 'linear-gradient(180deg, rgba(255,252,242,0.98) 0%, rgba(255,247,228,0.95) 100%)',
48
+ header: 'linear-gradient(180deg, rgba(255,243,208,0.98), rgba(255,250,240,0.9))',
49
49
  },
50
+ } as const
51
+
52
+ function metricPill(label: string, value: number, color: string) {
53
+ return (
54
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 8px', borderRadius: 999, background: 'rgba(255,255,255,0.76)', border: '1px solid rgba(123,109,91,0.12)', fontSize: 9.5, fontWeight: 700, color }}>
55
+ <span>{label}</span>
56
+ <span>{value}</span>
57
+ </span>
58
+ )
50
59
  }
51
60
 
52
61
  function SkillFlowNode({ data }: { data: SkillNodeData }) {
53
62
  const style = CATEGORY_STYLES[data.category]
54
63
  const scorePercent = Math.round(data.score * 100)
55
- const scoreColor = data.score >= 0.8 ? '#059669' : data.score >= 0.6 ? '#d97706' : '#dc2626'
64
+ const scoreColor = data.score >= 0.8 ? '#0f8363' : data.score >= 0.6 ? '#c18a17' : '#cb4f40'
65
+ const summary = data.evolutionSummary[0] ?? data.description
66
+ const relationCount = data.inheritsFrom.length + data.children.length + data.enhances.length + data.conflicts.length
56
67
 
57
68
  return (
58
- <div style={{
59
- background: style.bg,
60
- border: `1.5px solid ${style.border}`,
61
- borderRadius: 14,
62
- width: 280,
63
- fontFamily: '-apple-system, BlinkMacSystemFont, SF Pro Display, system-ui, sans-serif',
64
- boxShadow: '0 2px 8px rgba(0,0,0,0.06), 0 0px 1px rgba(0,0,0,0.04)',
65
- overflow: 'hidden',
66
- transition: 'box-shadow 0.2s ease',
67
- }}>
69
+ <div
70
+ style={{
71
+ width: 304,
72
+ borderRadius: 18,
73
+ overflow: 'hidden',
74
+ border: `1.5px solid ${style.border}`,
75
+ background: style.bg,
76
+ boxShadow: '0 18px 34px rgba(48, 30, 12, 0.12), 0 2px 8px rgba(48, 30, 12, 0.05)',
77
+ backdropFilter: 'blur(12px)',
78
+ color: '#171613',
79
+ fontFamily: 'Inter, system-ui, sans-serif',
80
+ }}
81
+ >
68
82
  <Handle type="target" position={Position.Top} style={{ background: style.border, width: 8, height: 8, border: '2px solid #fff' }} />
69
83
  <Handle type="source" position={Position.Bottom} style={{ background: style.border, width: 8, height: 8, border: '2px solid #fff' }} />
70
84
  <Handle type="target" position={Position.Left} id="left" style={{ background: style.border, width: 6, height: 6, border: '2px solid #fff' }} />
71
85
  <Handle type="source" position={Position.Right} id="right" style={{ background: style.border, width: 6, height: 6, border: '2px solid #fff' }} />
72
86
 
73
- {/* Header */}
74
- <div style={{
75
- padding: '10px 16px',
76
- background: style.headerBg,
77
- borderBottom: `1px solid ${style.border}40`,
78
- display: 'flex',
79
- justifyContent: 'space-between',
80
- alignItems: 'flex-start',
81
- }}>
82
- <div style={{ flex: 1, minWidth: 0 }}>
83
- <div style={{
84
- fontSize: 9,
85
- fontWeight: 700,
86
- letterSpacing: 1.5,
87
- color: style.accent,
88
- textTransform: 'uppercase',
89
- marginBottom: 3,
90
- }}>
91
- {style.label}
92
- </div>
93
- <div style={{
94
- fontSize: 14,
95
- fontWeight: 700,
96
- color: '#1a1d2e',
97
- lineHeight: 1.3,
98
- overflow: 'hidden',
99
- textOverflow: 'ellipsis',
100
- whiteSpace: 'nowrap',
101
- }}>
102
- {data.name}
87
+ <div style={{ padding: '14px 16px 12px', background: style.header, borderBottom: `1px solid ${style.border}` }}>
88
+ <div style={{ display: 'flex', justifyContent: 'space-between', gap: 12, alignItems: 'flex-start' }}>
89
+ <div style={{ minWidth: 0, flex: 1 }}>
90
+ <div style={{ display: 'flex', alignItems: 'center', gap: 7, marginBottom: 6, flexWrap: 'wrap' }}>
91
+ <span style={{ display: 'inline-flex', alignItems: 'center', padding: '3px 8px', borderRadius: 999, background: `${style.accent}14`, color: style.accent, border: `1px solid ${style.accent}22`, fontSize: 9.5, fontWeight: 800, letterSpacing: '0.1em' }}>
92
+ {style.label}
93
+ </span>
94
+ <span style={{ fontSize: 9.5, color: '#6d675f', fontWeight: 700, letterSpacing: '0.08em' }}>
95
+ {data.layer.toUpperCase()}
96
+ </span>
97
+ </div>
98
+ <div style={{ fontSize: 15, fontWeight: 800, lineHeight: 1.22, letterSpacing: '-0.03em', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
99
+ {data.name}
100
+ </div>
103
101
  </div>
104
- </div>
105
- <div style={{
106
- display: 'flex',
107
- flexDirection: 'column',
108
- alignItems: 'center',
109
- marginLeft: 10,
110
- flexShrink: 0,
111
- }}>
112
- <div style={{
113
- fontSize: 20,
114
- fontWeight: 800,
115
- color: scoreColor,
116
- lineHeight: 1,
117
- }}>
118
- {scorePercent}
102
+
103
+ <div style={{ minWidth: 64, textAlign: 'right' }}>
104
+ <div style={{ fontSize: 24, fontWeight: 850, lineHeight: 1, color: scoreColor }}>{scorePercent}</div>
105
+ <div style={{ marginTop: 4, fontSize: 8.5, fontWeight: 800, letterSpacing: '0.16em', color: '#8f877d' }}>SCORE</div>
119
106
  </div>
120
- <div style={{ fontSize: 8, color: '#9ca3af', marginTop: 1, fontWeight: 500 }}>SCORE</div>
121
107
  </div>
122
- </div>
123
108
 
124
- {/* Body */}
125
- <div style={{ padding: '10px 16px 12px' }}>
126
- {/* Score Bar */}
127
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
128
- <div style={{ flex: 1, height: 4, background: '#e5e7eb', borderRadius: 2, overflow: 'hidden' }}>
129
- <div style={{ width: `${scorePercent}%`, height: '100%', background: `linear-gradient(90deg, ${style.accent}80, ${scoreColor})`, borderRadius: 2, transition: 'width 0.5s ease' }} />
109
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 12 }}>
110
+ <div style={{ flex: 1, height: 6, borderRadius: 999, overflow: 'hidden', background: 'rgba(108,99,90,0.14)' }}>
111
+ <div style={{ width: `${scorePercent}%`, height: '100%', borderRadius: 999, background: `linear-gradient(90deg, ${style.accent}, ${scoreColor})` }} />
130
112
  </div>
131
- {data.generation > 0 && (
132
- <span style={{
133
- fontSize: 9, fontWeight: 700, padding: '1px 6px', borderRadius: 4,
134
- background: '#dcfce7', color: '#059669',
135
- }}>gen {data.generation}</span>
136
- )}
113
+ {data.generation > 0 ? (
114
+ <span style={{ flexShrink: 0, padding: '3px 8px', borderRadius: 999, background: 'rgba(15,131,99,0.12)', color: '#0f8363', fontSize: 9.5, fontWeight: 800 }}>
115
+ gen {data.generation}
116
+ </span>
117
+ ) : null}
137
118
  </div>
119
+ </div>
138
120
 
139
- {/* Evolution Summary or Description */}
140
- {data.evolutionSummary.length > 0 ? (
141
- <div style={{ marginBottom: 8 }}>
142
- {data.evolutionSummary.slice(0, 2).map((s, i) => (
143
- <div key={i} style={{
144
- fontSize: 10, color: '#4b5563', lineHeight: 1.4, marginBottom: 3,
145
- paddingLeft: 8, borderLeft: `2px solid ${style.accent}40`,
146
- }}>
147
- {s.slice(0, 70)}{s.length > 70 ? '...' : ''}
148
- </div>
149
- ))}
150
- </div>
151
- ) : data.description ? (
152
- <div style={{ fontSize: 10, color: '#6b7280', lineHeight: 1.4, marginBottom: 8 }}>
153
- {data.description.slice(0, 90)}{data.description.length > 90 ? '...' : ''}
121
+ <div style={{ padding: '14px 16px 16px' }}>
122
+ {summary ? (
123
+ <div style={{ marginBottom: 12, padding: '10px 11px', borderRadius: 12, background: 'rgba(255,255,255,0.72)', border: '1px solid rgba(123,109,91,0.12)', fontSize: 10.5, lineHeight: 1.55, color: '#4d4942' }}>
124
+ {summary.slice(0, 118)}{summary.length > 118 ? '…' : ''}
154
125
  </div>
155
126
  ) : null}
156
127
 
157
- {/* Connection Indicators */}
158
- {(data.inheritsFrom.length > 0 || data.children.length > 0 || data.enhances.length > 0 || data.conflicts.length > 0) && (
159
- <div style={{ display: 'flex', gap: 6, marginBottom: 6, flexWrap: 'wrap' }}>
160
- {data.inheritsFrom.length > 0 && (
161
- <span style={{ fontSize: 9, color: '#818cf8', fontWeight: 500 }}>↑ {data.inheritsFrom.length}</span>
162
- )}
163
- {data.children.length > 0 && (
164
- <span style={{ fontSize: 9, color: '#818cf8', fontWeight: 500 }}>↓ {data.children.length}</span>
165
- )}
166
- {data.enhances.length > 0 && (
167
- <span style={{ fontSize: 9, color: '#22c55e', fontWeight: 500 }}>↔ {data.enhances.length}</span>
168
- )}
169
- {data.conflicts.length > 0 && (
170
- <span style={{ fontSize: 9, color: '#ef4444', fontWeight: 500 }}>⚡ {data.conflicts.length}</span>
171
- )}
172
- </div>
173
- )}
128
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: data.tags.length > 0 ? 10 : 0 }}>
129
+ {data.inheritsFrom.length > 0 ? metricPill('↑ parents', data.inheritsFrom.length, '#6b49df') : null}
130
+ {data.children.length > 0 ? metricPill('↓ children', data.children.length, '#2d66d8') : null}
131
+ {data.enhances.length > 0 ? metricPill('↔ enhances', data.enhances.length, '#0f8363') : null}
132
+ {data.conflicts.length > 0 ? metricPill('⚡ conflicts', data.conflicts.length, '#cb4f40') : null}
133
+ {relationCount === 0 ? metricPill('• isolated', 0, '#8c877f') : null}
134
+ </div>
174
135
 
175
- {/* Tags */}
176
- {data.tags.length > 0 && (
177
- <div style={{ display: 'flex', gap: 3, flexWrap: 'wrap' }}>
178
- {data.tags.slice(0, 4).map(t => (
179
- <span key={t} style={{
180
- fontSize: 9, padding: '1px 6px', borderRadius: 4,
181
- background: `${style.accent}10`, color: style.accent, fontWeight: 500,
182
- border: `1px solid ${style.accent}20`,
183
- }}>{t}</span>
136
+ {data.tags.length > 0 ? (
137
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
138
+ {data.tags.slice(0, 4).map((tag) => (
139
+ <span key={tag} style={{ display: 'inline-flex', alignItems: 'center', padding: '3px 8px', borderRadius: 999, background: `${style.accent}10`, color: style.accent, border: `1px solid ${style.accent}20`, fontSize: 9.5, fontWeight: 700 }}>
140
+ {tag}
141
+ </span>
184
142
  ))}
185
143
  </div>
186
- )}
144
+ ) : null}
187
145
  </div>
188
146
  </div>
189
147
  )
@@ -0,0 +1,25 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ export function ConsolePanel({
6
+ title,
7
+ tone = 'neutral',
8
+ actions,
9
+ children,
10
+ }: {
11
+ title: ReactNode
12
+ tone?: 'neutral' | 'green' | 'red' | 'yellow' | 'blue' | 'purple'
13
+ actions?: ReactNode
14
+ children: ReactNode
15
+ }) {
16
+ return (
17
+ <div className={`console-panel console-panel-${tone}`}>
18
+ <div className="console-panel-header">
19
+ <div className="console-panel-title">{title}</div>
20
+ {actions ? <div className="console-panel-actions">{actions}</div> : null}
21
+ </div>
22
+ <div className="console-panel-body">{children}</div>
23
+ </div>
24
+ )
25
+ }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+
3
+ import Link from 'next/link'
4
+ import type { ReactNode } from 'react'
5
+
6
+ function toneClass(tone?: string) {
7
+ return tone ? `metric-card-${tone}` : 'metric-card-neutral'
8
+ }
9
+
10
+ export function MetricCard({
11
+ label,
12
+ value,
13
+ sublabel,
14
+ icon,
15
+ tone = 'neutral',
16
+ href,
17
+ }: {
18
+ label: string
19
+ value: ReactNode
20
+ sublabel?: ReactNode
21
+ icon?: ReactNode
22
+ tone?: 'neutral' | 'purple' | 'green' | 'blue' | 'yellow' | 'red'
23
+ href?: string
24
+ }) {
25
+ const content = (
26
+ <div className={`metric-card ${toneClass(tone)} ${href ? 'metric-card-link' : ''}`}>
27
+ <div className="metric-card-header">
28
+ <div>
29
+ <div className="metric-card-label">{label}</div>
30
+ <div className="metric-card-value">{value}</div>
31
+ </div>
32
+ {icon ? <div className="metric-card-icon">{icon}</div> : null}
33
+ </div>
34
+ {sublabel ? <div className="metric-card-sublabel">{sublabel}</div> : null}
35
+ </div>
36
+ )
37
+
38
+ if (!href) return content
39
+
40
+ return (
41
+ <Link href={href} className="metric-card-anchor">
42
+ {content}
43
+ </Link>
44
+ )
45
+ }
@@ -11,105 +11,94 @@ interface OverviewActionsProps {
11
11
  projectList: string[]
12
12
  }
13
13
 
14
- // Small inline SVG flow diagrams for each action
15
14
  function FlowDiagram({ steps, color }: { steps: string[]; color: string }) {
16
15
  return (
17
- <div style={{ display: 'flex', alignItems: 'center', gap: 0, marginTop: 8, marginBottom: 2, flexWrap: 'wrap', rowGap: 4 }}>
18
- {steps.map((step, i) => (
19
- <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 0 }}>
20
- <div style={{
21
- fontSize: 9, fontWeight: 600, padding: '2px 6px',
22
- background: i === steps.length - 1 ? `${color}18` : 'var(--bg-section)',
23
- color: i === steps.length - 1 ? color : 'var(--text-dim)',
24
- borderRadius: 4, whiteSpace: 'nowrap',
25
- border: i === steps.length - 1 ? `1px solid ${color}30` : '1px solid var(--border-subtle)',
26
- }}>
16
+ <div className="flow-diagram">
17
+ {steps.map((step, index) => (
18
+ <div key={`${step}-${index}`} style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
19
+ <span className={`flow-step ${index === steps.length - 1 ? 'flow-step-terminal' : ''}`} style={{ color: index === steps.length - 1 ? color : 'var(--text-secondary)', background: index === steps.length - 1 ? `${color}16` : undefined }}>
27
20
  {step}
28
- </div>
29
- {i < steps.length - 1 && (
30
- <span style={{ fontSize: 9, color: 'var(--text-muted)', padding: '0 2px' }}>&rarr;</span>
31
- )}
21
+ </span>
22
+ {index < steps.length - 1 ? <span className="flow-connector">→</span> : null}
32
23
  </div>
33
24
  ))}
34
25
  </div>
35
26
  )
36
27
  }
37
28
 
38
- export function OverviewActions({ hasFailures, hasSkills, hasEdges, unresolvedCount, skillCount, projectList }: OverviewActionsProps) {
29
+ export function OverviewActions({ hasFailures, hasSkills, hasEdges, unresolvedCount, skillCount }: OverviewActionsProps) {
39
30
  const actions = [
40
31
  {
41
32
  id: 'graph-rebuild',
42
33
  label: 'Organize Skills',
43
- subtitle: `Analyze ${skillCount} skills to discover how they relate dependencies, enhancements, and conflicts.`,
34
+ subtitle: `Map how your ${skillCount} skills support, inherit from, or conflict with each other.`,
44
35
  command: 'graph-rebuild',
45
36
  icon: 'M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1',
46
37
  color: 'var(--purple)',
47
- description: 'Use LLM to infer relationships between skills',
38
+ description: 'Use LLM analysis to infer relationships between skills',
48
39
  disabled: !hasSkills,
49
- disabledReason: 'No skills imported yet — run helixevo init first',
50
- flowSteps: [`${skillCount} skills`, 'LLM analysis', 'Skill graph'],
40
+ disabledReason: 'No skills imported yet — run helixevo init first.',
41
+ flowSteps: [`${skillCount} skills`, 'Relationship scan', 'Network map'],
51
42
  },
52
43
  {
53
44
  id: 'generalize',
54
45
  label: 'Generalize',
55
- subtitle: 'Find common patterns across your skills and create higher-level abstract skills.',
46
+ subtitle: 'Promote repeated patterns upward into clearer, reusable abstractions.',
56
47
  command: 'generalize',
57
48
  icon: 'M5 10l7-7m0 0l7 7m-7-7v18',
58
49
  color: 'var(--blue)',
59
50
  description: 'Promote cross-skill patterns to abstract parent skills',
60
51
  disabled: !hasSkills,
61
- disabledReason: 'No skills imported yet',
62
- flowSteps: ['All skills', 'Pattern detection', 'Abstract skills'],
52
+ disabledReason: 'No skills imported yet.',
53
+ flowSteps: ['Patterns', 'Abstraction', 'Higher-level skills'],
63
54
  },
64
55
  {
65
56
  id: 'evolve',
66
- label: `Evolve${unresolvedCount > 0 ? ` (${unresolvedCount})` : ''}`,
57
+ label: unresolvedCount > 0 ? `Evolve (${unresolvedCount})` : 'Evolve',
67
58
  subtitle: hasFailures
68
- ? `Use ${unresolvedCount} correction${unresolvedCount !== 1 ? 's' : ''} to propose and test skill improvements.`
69
- : 'Use your corrections to improve skills. Corrections are captured automatically when you use Craft Agent.',
59
+ ? `Turn ${unresolvedCount} unresolved correction${unresolvedCount === 1 ? '' : 's'} into proposed, tested skill improvements.`
60
+ : 'Use captured corrections to propose, judge, and apply improvements.',
70
61
  command: 'evolve',
71
62
  icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6',
72
63
  color: 'var(--green)',
73
64
  description: 'Evolve skills based on captured corrections',
74
65
  disabled: !hasFailures,
75
- disabledReason: 'No corrections yet — use Craft Agent normally. When you correct its output, those corrections are captured automatically.',
76
- flowSteps: hasFailures
77
- ? [`${unresolvedCount} corrections`, 'Propose changes', 'Judge & test', 'Apply']
78
- : ['Corrections', 'Propose', 'Judge', 'Apply'],
66
+ disabledReason: 'No corrections captured yet — once you correct Craft Agent output, they are recorded automatically.',
67
+ flowSteps: hasFailures ? [`${unresolvedCount} corrections`, 'Propose', 'Judge + regress', 'Apply'] : ['Corrections', 'Propose', 'Judge', 'Apply'],
79
68
  },
80
69
  {
81
70
  id: 'health',
82
71
  label: 'Health Check',
83
- subtitle: 'Assess your skill network for coverage gaps, cohesion issues, and imbalances.',
72
+ subtitle: 'Assess coverage gaps, cohesion, balance, and transfer potential across the network.',
84
73
  command: 'health',
85
74
  icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z',
86
75
  color: 'var(--yellow)',
87
76
  description: 'Assess network cohesion and coverage',
88
77
  disabled: !hasSkills,
89
- disabledReason: 'No skills imported yet',
90
- flowSteps: ['Skill network', 'Coverage analysis', 'Health report'],
78
+ disabledReason: 'No skills imported yet.',
79
+ flowSteps: ['Network', 'Coverage scan', 'Health report'],
91
80
  },
92
81
  {
93
82
  id: 'graph-optimize',
94
83
  label: 'Optimize Network',
95
- subtitle: 'Find skills that should be merged, split, or that conflict with each other.',
84
+ subtitle: 'Detect skill overlaps, fragmentation, and merge/split opportunities.',
96
85
  command: 'graph-optimize',
97
86
  icon: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',
98
87
  color: 'var(--text-secondary)',
99
- description: 'Detect merge/split opportunities',
88
+ description: 'Detect merge and split opportunities in the skill graph',
100
89
  disabled: !hasEdges,
101
- disabledReason: 'Run "Organize Skills" first to discover relationships between skills.',
102
- flowSteps: ['Relationships', 'Detect overlaps', 'Merge/split/flag'],
90
+ disabledReason: 'Run Organize Skills first to generate relationships.',
91
+ flowSteps: ['Relationships', 'Overlap scan', 'Merge / split / flag'],
103
92
  },
104
93
  {
105
94
  id: 'research',
106
95
  label: 'Discover New Skills',
107
- subtitle: `Search the web for techniques your ${skillCount} skills don't cover yet, then draft new skills.`,
96
+ subtitle: `Search for techniques your current ${skillCount} skills still dont cover.`,
108
97
  command: 'research',
109
98
  icon: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',
110
99
  color: 'var(--purple)',
111
- description: 'Proactive web research discover draft test',
112
- flowSteps: ['Find gaps', 'Web search', 'Draft skills', 'Test & score'],
100
+ description: 'Run proactive research to discover and draft new skills',
101
+ flowSteps: ['Find gaps', 'Web research', 'Draft skills', 'Score'],
113
102
  },
114
103
  ]
115
104
 
@@ -0,0 +1,44 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ interface HeroChip {
6
+ label: string
7
+ tone?: 'neutral' | 'purple' | 'green' | 'blue' | 'yellow' | 'red'
8
+ }
9
+
10
+ export function PageHero({
11
+ eyebrow,
12
+ title,
13
+ description,
14
+ chips,
15
+ actions,
16
+ }: {
17
+ eyebrow?: string
18
+ title: string
19
+ description: ReactNode
20
+ chips?: HeroChip[]
21
+ actions?: ReactNode
22
+ }) {
23
+ return (
24
+ <section className="page-hero-card">
25
+ <div className="page-hero-grid">
26
+ <div>
27
+ {eyebrow && <div className="page-eyebrow">{eyebrow}</div>}
28
+ <h1 className="page-title">{title}</h1>
29
+ <p className="page-desc">{description}</p>
30
+ {chips && chips.length > 0 && (
31
+ <div className="hero-chip-row">
32
+ {chips.map((chip, index) => (
33
+ <span key={`${chip.label}-${index}`} className={`hero-chip hero-chip-${chip.tone ?? 'neutral'}`}>
34
+ {chip.label}
35
+ </span>
36
+ ))}
37
+ </div>
38
+ )}
39
+ </div>
40
+ {actions ? <div className="page-hero-actions">{actions}</div> : null}
41
+ </div>
42
+ </section>
43
+ )
44
+ }