helixevo 0.2.38 โ†’ 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.
@@ -1,13 +1,27 @@
1
1
  'use client'
2
2
 
3
- import { useState, useRef } from 'react'
3
+ import { useRef, useState } from 'react'
4
+ import { ConsolePanel } from '@/components/console-panel'
5
+ import { MetricCard } from '@/components/metric-card'
6
+ import { PageHero } from '@/components/page-hero'
7
+ import { SectionFrame } from '@/components/section-frame'
4
8
 
5
9
  interface Discovery {
6
- id: string; title: string; summary: string; score: number; gap: string; lastAccessed: string
10
+ id: string
11
+ title: string
12
+ summary: string
13
+ score: number
14
+ gap: string
15
+ lastAccessed: string
7
16
  }
8
17
 
9
18
  interface Draft {
10
- id: string; skillName: string; avgScore: number; passRate: number; iteration: number; hypothesis: string
19
+ id: string
20
+ skillName: string
21
+ avgScore: number
22
+ passRate: number
23
+ iteration: number
24
+ hypothesis: string
11
25
  }
12
26
 
13
27
  interface KnowledgeBuffer {
@@ -25,41 +39,48 @@ type RunState = 'idle' | 'running' | 'success' | 'error' | 'stopped'
25
39
  const PIPELINE_STEPS = [
26
40
  {
27
41
  step: 1,
28
- title: 'Identify Gaps',
29
- desc: 'Analyze your existing skills and project context to find what capabilities are missing.',
30
- icon: '๐Ÿ”',
42
+ title: 'Identify gaps',
43
+ desc: 'Analyze the existing network to detect missing capabilities or weak coverage areas.',
44
+ icon: 'โ—Ž',
31
45
  color: 'var(--blue)',
32
- bgColor: 'var(--blue-light)',
46
+ tone: 'blue' as const,
33
47
  },
34
48
  {
35
49
  step: 2,
36
- title: 'Web Search',
37
- desc: 'Search the web for the latest best practices, tools, and techniques to fill each gap.',
38
- icon: '๐ŸŒ',
50
+ title: 'Search the web',
51
+ desc: 'Pull in current techniques, patterns, and best practices that can close those gaps.',
52
+ icon: 'โ—Œ',
39
53
  color: 'var(--purple)',
40
- bgColor: 'var(--purple-light)',
54
+ tone: 'purple' as const,
41
55
  },
42
56
  {
43
57
  step: 3,
44
- title: 'Draft Skills',
45
- desc: 'Generate hypotheses and draft new SKILL.md files from the discoveries.',
46
- icon: '๐Ÿ“',
58
+ title: 'Draft skills',
59
+ desc: 'Translate discoveries into candidate SKILL.md drafts and experimental hypotheses.',
60
+ icon: 'โœŽ',
47
61
  color: 'var(--yellow)',
48
- bgColor: 'var(--yellow-light)',
62
+ tone: 'yellow' as const,
49
63
  },
50
64
  {
51
65
  step: 4,
52
- title: 'Test & Score',
53
- desc: 'Test each draft against scenarios. Skills scoring โ‰ฅ7/10 with โ‰ฅ67% pass rate are accepted.',
66
+ title: 'Test and score',
67
+ desc: 'Run scoring and pass-rate checks before a draft can become a stable part of the system.',
54
68
  icon: 'โœ“',
55
69
  color: 'var(--green)',
56
- bgColor: 'var(--green-light)',
70
+ tone: 'green' as const,
57
71
  },
58
72
  ]
59
73
 
74
+ function consoleTone(state: RunState): 'neutral' | 'green' | 'red' | 'yellow' {
75
+ if (state === 'success') return 'green'
76
+ if (state === 'error') return 'red'
77
+ if (state === 'stopped') return 'yellow'
78
+ return 'neutral'
79
+ }
80
+
60
81
  export default function ResearchClient({ buffer, projects }: Props) {
61
82
  const [runState, setRunState] = useState<RunState>('idle')
62
- const [output, setOutput] = useState<string>('')
83
+ const [output, setOutput] = useState('')
63
84
  const abortRef = useRef<AbortController | null>(null)
64
85
  const outputRef = useRef<HTMLPreElement | null>(null)
65
86
 
@@ -120,7 +141,8 @@ export default function ResearchClient({ buffer, projects }: Props) {
120
141
  }
121
142
  }
122
143
  }
123
- if (runState === 'running') setRunState('success')
144
+
145
+ setRunState(prev => prev === 'running' ? 'success' : prev)
124
146
  } catch (err: unknown) {
125
147
  if (err instanceof Error && err.name === 'AbortError') {
126
148
  try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
@@ -140,257 +162,167 @@ export default function ResearchClient({ buffer, projects }: Props) {
140
162
  try { await fetch('/api/run', { method: 'DELETE' }) } catch {}
141
163
  }
142
164
 
143
- const nearPass = buffer.drafts.filter(d => d.passRate >= 0.67)
165
+ const nearPass = buffer.drafts.filter((draft) => draft.passRate >= 0.67)
144
166
 
145
167
  return (
146
- <div>
147
- <div className="page-header">
148
- <h1 className="page-title">Proactive Research</h1>
149
- <p className="page-desc">
150
- Discover new skills by searching the web for techniques and best practices your skill set doesn&apos;t cover yet
151
- </p>
152
- </div>
168
+ <div style={{ display: 'grid', gap: 22 }}>
169
+ <PageHero
170
+ eyebrow="Discovery lab"
171
+ title="Proactive Research"
172
+ description="Search for techniques your network still lacks, turn discoveries into draft skills, and score them before they enter the evolutionary loop."
173
+ chips={[
174
+ { label: `${projects.length} tracked projects`, tone: projects.length > 0 ? 'blue' : 'neutral' },
175
+ { label: `${buffer.discoveries.length} discoveries`, tone: 'purple' },
176
+ { label: `${buffer.drafts.length} drafts`, tone: 'yellow' },
177
+ { label: `${nearPass.length} near-pass drafts`, tone: 'green' },
178
+ ]}
179
+ />
153
180
 
154
- {/* Pipeline Visualization */}
155
- <div className="card" style={{ marginBottom: 24, padding: '20px 24px' }}>
156
- <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: 1.5, marginBottom: 16 }}>
157
- How Research Works
158
- </div>
159
- <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0 }}>
160
- {PIPELINE_STEPS.map((step, i) => (
161
- <div key={step.step} style={{ display: 'flex', alignItems: 'flex-start' }}>
162
- <div style={{ flex: 1 }}>
163
- <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
164
- <div style={{
165
- width: 32, height: 32, borderRadius: 9,
166
- background: step.bgColor,
167
- display: 'flex', alignItems: 'center', justifyContent: 'center',
168
- fontSize: 14, flexShrink: 0,
169
- }}>
170
- {step.icon}
171
- </div>
172
- <div>
173
- <div style={{ fontSize: 9, fontWeight: 600, color: step.color, letterSpacing: 0.5 }}>STEP {step.step}</div>
174
- <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)' }}>{step.title}</div>
175
- </div>
176
- </div>
177
- <div style={{ fontSize: 11, color: 'var(--text-dim)', lineHeight: 1.5, paddingRight: 12 }}>
178
- {step.desc}
181
+ <SectionFrame
182
+ eyebrow="Pipeline"
183
+ title="How research works"
184
+ description="Research moves from network gaps to external evidence, then to draft skills, then through scoring and pass-rate thresholds."
185
+ actions={
186
+ runState === 'running' ? (
187
+ <button onClick={handleStop} className="badge badge-red" style={{ border: 'none', cursor: 'pointer' }}>Stop research</button>
188
+ ) : (
189
+ <button onClick={handleRun} className="badge badge-purple" style={{ border: 'none', cursor: 'pointer' }}>Run research</button>
190
+ )
191
+ }
192
+ >
193
+ <div className="grid-4" style={{ marginBottom: 18 }}>
194
+ {PIPELINE_STEPS.map((step) => (
195
+ <div key={step.step} className={`metric-card metric-card-${step.tone}`}>
196
+ <div className="metric-card-header">
197
+ <div>
198
+ <div className="metric-card-label">Step {step.step}</div>
199
+ <div className="metric-card-value" style={{ fontSize: 24 }}>{step.title}</div>
179
200
  </div>
201
+ <div className="metric-card-icon" style={{ color: step.color }}>{step.icon}</div>
180
202
  </div>
181
- {i < 3 && (
182
- <svg width="20" height="60" viewBox="0 0 20 60" style={{ flexShrink: 0, marginTop: 8 }}>
183
- <path d="M4 30h12M12 24l4 6-4 6" stroke="var(--border)" strokeWidth="1.5" fill="none" strokeLinecap="round" strokeLinejoin="round" />
184
- </svg>
185
- )}
203
+ <div className="metric-card-sublabel">{step.desc}</div>
186
204
  </div>
187
205
  ))}
188
206
  </div>
189
207
 
190
- {/* Run button */}
191
- <div style={{ marginTop: 20, paddingTop: 16, borderTop: '1px solid var(--border)' }}>
192
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
193
- {runState === 'idle' && (
194
- <button
195
- onClick={handleRun}
196
- style={{
197
- display: 'flex', alignItems: 'center', gap: 6,
198
- padding: '9px 20px',
199
- background: 'var(--teal, var(--green))',
200
- color: '#fff', border: 'none',
201
- borderRadius: 'var(--radius)',
202
- fontSize: 13, fontWeight: 600, cursor: 'pointer',
203
- transition: 'opacity 0.15s',
204
- }}
205
- onMouseOver={e => (e.currentTarget.style.opacity = '0.9')}
206
- onMouseOut={e => (e.currentTarget.style.opacity = '1')}
207
- >
208
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
209
- <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
210
- </svg>
211
- Run Research
212
- </button>
213
- )}
214
- {runState === 'running' && (
215
- <button
216
- onClick={handleStop}
217
- style={{
218
- display: 'flex', alignItems: 'center', gap: 6,
219
- padding: '9px 20px',
220
- background: 'var(--red)', color: '#fff', border: 'none',
221
- borderRadius: 'var(--radius)',
222
- fontSize: 13, fontWeight: 600, cursor: 'pointer',
223
- }}
224
- >
225
- <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor" stroke="none">
226
- <rect x="4" y="4" width="16" height="16" rx="2" />
227
- </svg>
228
- Stop Research
229
- </button>
230
- )}
231
- {(runState === 'success' || runState === 'error' || runState === 'stopped') && (
232
- <div style={{ display: 'flex', gap: 8 }}>
233
- <button onClick={handleRun} style={{
234
- padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
235
- borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
236
- color: 'var(--text-secondary)',
237
- }}>
238
- Run Again
239
- </button>
240
- <button onClick={() => window.location.reload()} style={{
241
- padding: '8px 16px', background: 'var(--bg-section)', border: '1px solid var(--border)',
242
- borderRadius: 'var(--radius)', fontSize: 12, fontWeight: 600, cursor: 'pointer',
243
- color: 'var(--text-secondary)',
244
- }}>
245
- Refresh Data
246
- </button>
247
- </div>
248
- )}
249
- <span style={{ fontSize: 11, color: 'var(--text-muted)' }}>
250
- {runState === 'running' && 'Searching the web and testing hypotheses...'}
251
- {runState === 'success' && 'Research complete โ€” refresh to see new discoveries'}
252
- {runState === 'error' && 'Research failed โ€” check output below'}
253
- {runState === 'stopped' && 'Research stopped'}
254
- {runState === 'idle' && 'Analyzes all your skills, searches for gaps, and drafts new skills'}
255
- </span>
256
- </div>
208
+ <div className="signal-text">
209
+ {runState === 'running' && 'The research engine is currently searching, drafting, and testing hypotheses.'}
210
+ {runState === 'success' && 'Research completed โ€” refresh the page if you want to reload persisted discoveries.'}
211
+ {runState === 'error' && 'Research failed โ€” check the run console below for details.'}
212
+ {runState === 'stopped' && 'Research was stopped before completion.'}
213
+ {runState === 'idle' && 'Run the pipeline to discover new capabilities, test draft skills, and surface the most promising additions to your network.'}
257
214
  </div>
258
- </div>
215
+ </SectionFrame>
259
216
 
260
- {/* Output panel */}
261
- {runState !== 'idle' && (
262
- <div className="card" style={{ marginBottom: 24, overflow: 'hidden' }}>
263
- <div style={{
264
- padding: '10px 16px',
265
- background: runState === 'success' ? 'var(--green-light)' : runState === 'error' ? 'var(--red-light)' : runState === 'stopped' ? 'var(--yellow-light)' : 'var(--bg-section)',
266
- borderBottom: '1px solid var(--border)',
267
- fontSize: 12, fontWeight: 600,
268
- color: runState === 'success' ? 'var(--green)' : runState === 'error' ? 'var(--red)' : runState === 'stopped' ? 'var(--yellow)' : 'var(--text-secondary)',
269
- }}>
270
- {runState === 'running' && 'โ— Running research pipeline...'}
271
- {runState === 'success' && 'โœ“ Research completed'}
272
- {runState === 'error' && 'โœ— Research failed'}
273
- {runState === 'stopped' && 'โ–  Research stopped'}
274
- </div>
275
- <pre ref={outputRef} style={{
276
- padding: '14px 16px', margin: 0,
277
- fontSize: 11, lineHeight: 1.55,
278
- fontFamily: 'var(--font-mono)',
279
- color: 'var(--text-secondary)',
280
- maxHeight: 400, overflow: 'auto',
281
- whiteSpace: 'pre-wrap', wordBreak: 'break-word',
282
- }}>
283
- {output || 'Starting research pipeline...'}
217
+ {runState !== 'idle' ? (
218
+ <ConsolePanel
219
+ tone={consoleTone(runState)}
220
+ title={
221
+ <span>
222
+ {runState === 'running' ? 'Research pipeline runningโ€ฆ' :
223
+ runState === 'success' ? 'Research completed' :
224
+ runState === 'stopped' ? 'Research stopped' :
225
+ 'Research failed'}
226
+ </span>
227
+ }
228
+ actions={
229
+ runState !== 'running' ? (
230
+ <div style={{ display: 'flex', gap: 8 }}>
231
+ <button onClick={handleRun} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Run again</button>
232
+ <button onClick={() => window.location.reload()} className="badge badge-gray" style={{ border: 'none', cursor: 'pointer' }}>Refresh data</button>
233
+ </div>
234
+ ) : null
235
+ }
236
+ >
237
+ <pre
238
+ ref={outputRef}
239
+ style={{
240
+ margin: 0,
241
+ padding: '16px 18px',
242
+ fontSize: 11.5,
243
+ lineHeight: 1.62,
244
+ color: 'var(--text-secondary)',
245
+ whiteSpace: 'pre-wrap',
246
+ wordBreak: 'break-word',
247
+ }}
248
+ >
249
+ {output || 'Starting research pipelineโ€ฆ'}
284
250
  </pre>
285
- </div>
286
- )}
251
+ </ConsolePanel>
252
+ ) : null}
287
253
 
288
- {/* Summary Stats */}
289
- <div className="grid-3" style={{ marginBottom: 24 }}>
290
- <div className="stat-card">
291
- <div className="stat-value" style={{ color: 'var(--blue)' }}>{buffer.discoveries.length}</div>
292
- <div className="stat-label">Discoveries</div>
293
- <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 50 capacity</div>
294
- </div>
295
- <div className="stat-card">
296
- <div className="stat-value" style={{ color: 'var(--yellow)' }}>{buffer.drafts.length}</div>
297
- <div className="stat-label">Drafts In Progress</div>
298
- <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>of 10 capacity</div>
299
- </div>
300
- <div className="stat-card">
301
- <div className="stat-value" style={{ color: 'var(--green)' }}>{nearPass.length}</div>
302
- <div className="stat-label">Near-Pass Drafts</div>
303
- <div style={{ fontSize: 10, color: 'var(--text-muted)', marginTop: 2 }}>โ‰ฅ67% pass rate โ€” close to becoming skills</div>
304
- </div>
254
+ <div className="grid-3">
255
+ <MetricCard label="Discoveries" value={buffer.discoveries.length} sublabel="Research findings currently in the knowledge buffer" tone="blue" icon="โ—Ž" />
256
+ <MetricCard label="Drafts in progress" value={buffer.drafts.length} sublabel="Experimental skills still being refined" tone="yellow" icon="โœŽ" />
257
+ <MetricCard label="Near-pass drafts" value={nearPass.length} sublabel="Drafts already above the pass-rate threshold" tone="green" icon="โ†—" />
305
258
  </div>
306
259
 
307
260
  <div className="grid-2">
308
- {/* Discoveries */}
309
- <div className="card">
310
- <div className="card-body">
311
- <div className="card-header-label">Discoveries ({buffer.discoveries.length}/50)</div>
312
- {buffer.discoveries.length === 0 ? (
313
- <div className="empty-state" style={{ padding: 32 }}>
314
- <div className="empty-state-icon">
315
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
316
- <path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
317
- </svg>
318
- </div>
319
- <div className="empty-state-title">No discoveries yet</div>
320
- <div className="empty-state-desc">
321
- Click <strong>Run Research</strong> above to search the web for techniques your skills don&apos;t cover
322
- </div>
323
- </div>
324
- ) : (
325
- buffer.discoveries.sort((a, b) => b.score - a.score).map(d => (
326
- <div key={d.id} style={{
327
- padding: '12px 0',
328
- borderBottom: '1px solid var(--border-subtle)',
329
- }}>
330
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
331
- <span style={{ fontSize: 13, fontWeight: 600, lineHeight: 1.3 }}>{d.title}</span>
332
- <span className="score-pill" style={{ color: 'var(--blue)', flexShrink: 0, marginLeft: 8 }}>
333
- {(d.score * 100).toFixed(0)}%
334
- </span>
335
- </div>
336
- <div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
337
- {d.summary.slice(0, 140)}
261
+ <SectionFrame
262
+ eyebrow="Knowledge buffer"
263
+ title={`Discoveries (${buffer.discoveries.length}/50)`}
264
+ description="External techniques, patterns, and signals the research system believes are worth turning into skills."
265
+ tone="blue"
266
+ >
267
+ {buffer.discoveries.length === 0 ? (
268
+ <div className="empty-state" style={{ padding: 24 }}>
269
+ <div className="empty-state-title">No discoveries yet</div>
270
+ <div className="empty-state-desc">Run research to search the web for techniques your skill network does not cover yet.</div>
271
+ </div>
272
+ ) : (
273
+ <div className="summary-list">
274
+ {buffer.discoveries.sort((a, b) => b.score - a.score).map((discovery) => (
275
+ <div key={discovery.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
276
+ <div className="summary-row-main">
277
+ <div className="summary-row-title">{discovery.title}</div>
278
+ <div className="summary-row-meta">{discovery.summary.slice(0, 150)}{discovery.summary.length > 150 ? 'โ€ฆ' : ''}</div>
279
+ <div style={{ display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
280
+ <span className="badge badge-blue">{discovery.gap}</span>
281
+ <span className="score-pill" style={{ color: 'var(--blue)' }}>{(discovery.score * 100).toFixed(0)}%</span>
282
+ </div>
338
283
  </div>
339
- <span className="badge badge-blue">{d.gap}</span>
340
284
  </div>
341
- ))
342
- )}
343
- </div>
344
- </div>
285
+ ))}
286
+ </div>
287
+ )}
288
+ </SectionFrame>
345
289
 
346
- {/* Drafts */}
347
- <div className="card">
348
- <div className="card-body">
349
- <div className="card-header-label">Skill Drafts ({buffer.drafts.length}/10)</div>
350
- {buffer.drafts.length === 0 ? (
351
- <div className="empty-state" style={{ padding: 32 }}>
352
- <div className="empty-state-icon">
353
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
354
- <path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
355
- </svg>
356
- </div>
357
- <div className="empty-state-title">No drafts yet</div>
358
- <div className="empty-state-desc">
359
- Drafts are skill prototypes that almost passed quality checks. Running research again improves them iteratively.
360
- </div>
361
- </div>
362
- ) : (
363
- buffer.drafts.sort((a, b) => b.avgScore - a.avgScore).map(d => (
364
- <div key={d.id} style={{
365
- padding: '12px 0',
366
- borderBottom: '1px solid var(--border-subtle)',
367
- }}>
368
- <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 4 }}>
369
- <span style={{ fontSize: 13, fontWeight: 600 }}>{d.skillName}</span>
370
- <span className={`badge ${d.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>
371
- {d.avgScore.toFixed(1)}/10 ยท iter {d.iteration}
372
- </span>
373
- </div>
374
- <div style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.5, marginBottom: 6 }}>
375
- {d.hypothesis.slice(0, 140)}
290
+ <SectionFrame
291
+ eyebrow="Draft pipeline"
292
+ title={`Skill drafts (${buffer.drafts.length}/10)`}
293
+ description="Drafts are candidates that are close to graduating into stable skills but still need evidence, quality, or pass-rate improvements."
294
+ tone="yellow"
295
+ >
296
+ {buffer.drafts.length === 0 ? (
297
+ <div className="empty-state" style={{ padding: 24 }}>
298
+ <div className="empty-state-title">No drafts yet</div>
299
+ <div className="empty-state-desc">Run research to generate the first draft skills and evaluate them against the current thresholds.</div>
300
+ </div>
301
+ ) : (
302
+ <div className="summary-list">
303
+ {buffer.drafts.sort((a, b) => b.avgScore - a.avgScore).map((draft) => (
304
+ <div key={draft.id} className="summary-row" style={{ alignItems: 'flex-start' }}>
305
+ <div className="summary-row-main">
306
+ <div className="summary-row-title">{draft.skillName}</div>
307
+ <div className="summary-row-meta">{draft.hypothesis.slice(0, 150)}{draft.hypothesis.length > 150 ? 'โ€ฆ' : ''}</div>
308
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginTop: 10 }}>
309
+ <span className={`badge ${draft.avgScore >= 6 ? 'badge-green' : 'badge-yellow'}`}>{draft.avgScore.toFixed(1)}/10</span>
310
+ <span className="badge badge-gray">iter {draft.iteration}</span>
311
+ </div>
376
312
  </div>
377
- <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
378
- <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>Pass rate:</span>
379
- <div className="score-track" style={{ width: 60, height: 4 }}>
380
- <div className="score-fill" style={{
381
- width: `${d.passRate * 100}%`,
382
- background: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)',
383
- }} />
313
+ <div style={{ minWidth: 104 }}>
314
+ <div className="score-track" style={{ marginBottom: 6 }}>
315
+ <div className="score-fill" style={{ width: `${draft.passRate * 100}%`, background: draft.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }} />
316
+ </div>
317
+ <div style={{ textAlign: 'right', fontSize: 12, fontWeight: 800, color: draft.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
318
+ {(draft.passRate * 100).toFixed(0)}%
384
319
  </div>
385
- <b style={{ fontSize: 11, color: d.passRate >= 0.67 ? 'var(--green)' : 'var(--yellow)' }}>
386
- {(d.passRate * 100).toFixed(0)}%
387
- </b>
388
320
  </div>
389
321
  </div>
390
- ))
391
- )}
392
- </div>
393
- </div>
322
+ ))}
323
+ </div>
324
+ )}
325
+ </SectionFrame>
394
326
  </div>
395
327
  </div>
396
328
  )