vxkyyy commited on
Commit
ea86270
Β·
1 Parent(s): db4ae31

feat: premium Claude-style UI for HITL approval cards and autonomous result screen

Browse files
web/src/components/ApprovalCard.tsx CHANGED
@@ -18,8 +18,16 @@ interface Props {
18
  isSubmitting: boolean;
19
  }
20
 
21
- function fmt(name: string): string {
22
- return name.replace(/_/g, ' ');
 
 
 
 
 
 
 
 
23
  }
24
 
25
  export const ApprovalCard: React.FC<Props> = ({ data, onApprove, onReject, isSubmitting }) => {
@@ -27,7 +35,10 @@ export const ApprovalCard: React.FC<Props> = ({ data, onApprove, onReject, isSub
27
  const [feedback, setFeedback] = useState('');
28
 
29
  const hasWarnings = data.warnings && data.warnings.length > 0;
30
- const hasErrors = data.decisions?.some(d => /error|fail/i.test(d));
 
 
 
31
 
32
  const handleReject = () => {
33
  onReject(feedback);
@@ -36,72 +47,71 @@ export const ApprovalCard: React.FC<Props> = ({ data, onApprove, onReject, isSub
36
  };
37
 
38
  return (
39
- <div className="hitl-approval">
40
- {/* Main single-line content */}
41
- <div className="hitl-approval-row">
42
- <div className="hitl-approval-left">
43
- {(hasWarnings || hasErrors) && (
44
- <span
45
- className={`hitl-approval-dot ${hasErrors ? 'hitl-dot--error' : 'hitl-dot--warn'}`}
46
- />
47
- )}
48
- <span className="hitl-approval-stage">{fmt(data.stage_name)}</span>
49
- </div>
50
- <p className="hitl-approval-summary">
51
- {data.summary || `${fmt(data.stage_name)} completed successfully.`}
52
- </p>
53
- <div className="hitl-approval-actions">
54
- {!showFeedback && (
55
- <button className="hitl-fb-link" onClick={() => setShowFeedback(true)}>
56
- give feedback
57
- </button>
58
- )}
59
- <button
60
- className="hitl-continue"
61
- onClick={onApprove}
62
- disabled={isSubmitting}
63
- >
64
- {isSubmitting ? 'Continuing…' : 'Continue β†’'}
65
- </button>
66
  </div>
 
 
 
 
 
67
  </div>
68
 
69
- {/* Inline feedback field */}
70
- {showFeedback && (
71
- <div className="hitl-approval-feedback">
72
- <input
73
- className="hitl-fb-input"
74
- type="text"
75
- placeholder="What should the agent do differently?"
76
- value={feedback}
77
- onChange={e => setFeedback(e.target.value)}
78
- autoFocus
79
- onKeyDown={e => {
80
- if (e.key === 'Enter') handleReject();
81
- if (e.key === 'Escape') {
82
- setShowFeedback(false);
83
- setFeedback('');
84
- }
85
- }}
86
- />
87
- <button
88
- className="hitl-reject-pill"
89
- onClick={handleReject}
90
- disabled={isSubmitting}
91
- >
92
- {isSubmitting ? 'Rejecting…' : 'Reject'}
93
- </button>
94
- <button
95
- className="hitl-fb-cancel"
96
- onClick={() => {
97
- setShowFeedback(false);
98
- setFeedback('');
99
- }}
100
- >
101
- Γ—
102
- </button>
103
  </div>
104
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
  );
107
  };
 
18
  isSubmitting: boolean;
19
  }
20
 
21
+ const STAGE_ICONS: Record<string, string> = {
22
+ INIT: 'βš™', SPEC: 'β—ˆ', RTL_GEN: '⌨', RTL_FIX: 'β—ͺ',
23
+ VERIFICATION: 'β—‰', FORMAL_VERIFY: 'β—ˆ', COVERAGE_CHECK: 'β—Ž',
24
+ REGRESSION: 'β†Ί', SDC_GEN: 'β§—', FLOORPLAN: 'β–£',
25
+ HARDENING: '⬑', CONVERGENCE_REVIEW: 'β—Ž', ECO_PATCH: '⟴',
26
+ SIGNOFF: 'βœ“', SUCCESS: '✦', FAIL: 'βœ—',
27
+ };
28
+
29
+ function fmtStage(name: string): string {
30
+ return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
31
  }
32
 
33
  export const ApprovalCard: React.FC<Props> = ({ data, onApprove, onReject, isSubmitting }) => {
 
35
  const [feedback, setFeedback] = useState('');
36
 
37
  const hasWarnings = data.warnings && data.warnings.length > 0;
38
+ const hasErrors = data.decisions?.some((d: string) => /error|fail/i.test(d));
39
+ const artifactCount = data.artifacts?.length || 0;
40
+ const hasNext = data.next_stage_name && data.next_stage_name !== 'DONE';
41
+ const icon = STAGE_ICONS[data.stage_name] || 'β—†';
42
 
43
  const handleReject = () => {
44
  onReject(feedback);
 
47
  };
48
 
49
  return (
50
+ <div className={`ac-card ${hasErrors ? 'ac-card--error' : hasWarnings ? 'ac-card--warn' : 'ac-card--ok'}`}>
51
+
52
+ {/* Header β€” stage identity */}
53
+ <div className="ac-header">
54
+ <div className="ac-stage-id">
55
+ <span className="ac-stage-symbol">{icon}</span>
56
+ <span className="ac-stage-label">{fmtStage(data.stage_name)}</span>
57
+ {hasErrors && <span className="ac-badge ac-badge--error">Issue detected</span>}
58
+ {!hasErrors && hasWarnings && <span className="ac-badge ac-badge--warn">Warning</span>}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  </div>
60
+ {artifactCount > 0 && (
61
+ <span className="ac-artifact-pill">
62
+ {artifactCount} artifact{artifactCount !== 1 ? 's' : ''}
63
+ </span>
64
+ )}
65
  </div>
66
 
67
+ {/* Summary β€” primary content */}
68
+ <p className="ac-summary">
69
+ {data.summary || `${fmtStage(data.stage_name)} completed successfully.`}
70
+ </p>
71
+
72
+ {/* Next stage preview */}
73
+ {hasNext && data.next_stage_preview && (
74
+ <div className="ac-next-hint">
75
+ <span className="ac-next-arrow">↓</span>
76
+ <span className="ac-next-text">{data.next_stage_preview}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  </div>
78
  )}
79
+
80
+ {/* Action footer */}
81
+ <div className="ac-footer">
82
+ {!showFeedback ? (
83
+ <>
84
+ <button className="ac-give-feedback" onClick={() => setShowFeedback(true)}>
85
+ Give feedback
86
+ </button>
87
+ <button className="ac-continue-btn" onClick={onApprove} disabled={isSubmitting}>
88
+ {isSubmitting ? 'Continuing…' : 'Continue'}
89
+ {!isSubmitting && <span className="ac-chevron">β†’</span>}
90
+ </button>
91
+ </>
92
+ ) : (
93
+ <div className="ac-feedback-row">
94
+ <input
95
+ className="ac-feedback-input"
96
+ type="text"
97
+ placeholder="What should the agent do differently?"
98
+ value={feedback}
99
+ onChange={e => setFeedback(e.target.value)}
100
+ autoFocus
101
+ onKeyDown={e => {
102
+ if (e.key === 'Enter') handleReject();
103
+ if (e.key === 'Escape') { setShowFeedback(false); setFeedback(''); }
104
+ }}
105
+ />
106
+ <button className="ac-reject-btn" onClick={handleReject} disabled={isSubmitting}>
107
+ {isSubmitting ? 'Sending…' : 'Reject & redirect'}
108
+ </button>
109
+ <button className="ac-cancel-btn" onClick={() => { setShowFeedback(false); setFeedback(''); }}>
110
+ Cancel
111
+ </button>
112
+ </div>
113
+ )}
114
+ </div>
115
  </div>
116
  );
117
  };
web/src/components/ChipSummary.tsx CHANGED
@@ -9,6 +9,291 @@ interface Props {
9
  onReset: () => void;
10
  }
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  function MetricCard({ label, value, icon, color }: { label: string; value: any; icon: string; color: string }) {
13
  return (
14
  <motion.div
 
9
  onReset: () => void;
10
  }
11
 
12
+ function StatCell({ label, value, warn }: { label: string; value: any; warn?: boolean }) {
13
+ return (
14
+ <div className="cs-stat">
15
+ <span className={`cs-stat-value${warn ? ' cs-stat-value--warn' : ''}`}>
16
+ {value ?? 'β€”'}
17
+ </span>
18
+ <span className="cs-stat-label">{label}</span>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ function CheckRow({ label, passed, detail }: { label: string; passed: boolean | null; detail?: string }) {
24
+ const status = passed === true ? 'pass' : passed === false ? 'fail' : 'skip';
25
+ return (
26
+ <div className="cs-check-row">
27
+ <span className={`cs-check-icon cs-check-icon--${status}`}>
28
+ {status === 'pass' ? 'βœ“' : status === 'fail' ? 'βœ•' : '–'}
29
+ </span>
30
+ <div className="cs-check-body">
31
+ <span className="cs-check-label">{label}</span>
32
+ {detail && <span className="cs-check-detail">{detail}</span>}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export const ChipSummary: React.FC<Props> = ({ designName, result, jobStatus, events, onReset }) => {
39
+ const success = jobStatus === 'done';
40
+ const metrics = result?.metrics || {};
41
+ const convergence = result?.convergence_history || [];
42
+ const spec = result?.spec || '';
43
+ const rtlSnippet = result?.rtl_snippet || '';
44
+ const strategy = result?.strategy || '';
45
+ const buildTimeSec = result?.build_time_s || 0;
46
+ const buildTimeMin = Math.round(buildTimeSec / 60);
47
+ const coverage = result?.coverage || {};
48
+ const formalResult = result?.formal_result || '';
49
+ const signoffResult = result?.signoff_result || '';
50
+
51
+ const selfHeal = result?.self_heal || {
52
+ stage_exception_count: events.filter(e => /stage .* exception/i.test(e?.message || '')).length,
53
+ formal_regen_count: events.filter(e => /regenerating sva/i.test(e?.message || '')).length,
54
+ coverage_best_restore_count: events.filter(e => /restoring best testbench/i.test(e?.message || '')).length,
55
+ coverage_regression_reject_count: events.filter(e => /regressed coverage/i.test(e?.message || '')).length,
56
+ deterministic_tb_fallback_count: events.filter(e => /deterministic tb fallback/i.test(e?.message || '')).length,
57
+ };
58
+
59
+ const checkpointCount = events.filter(e => e.type === 'transition' || e.type === 'checkpoint').length;
60
+ const syntaxPassed = events.some(e => /syntax.*pass|lint.*clean/i.test(e?.message || ''));
61
+ const simPassed = events.some(e => /test passed|simulation.*pass/i.test(e?.message || ''));
62
+ const formalPassed = formalResult ? /pass|success/i.test(formalResult) : events.some(e => /formal.*pass/i.test(e?.message || ''));
63
+ const coveragePassed = events.some(e => /coverage passed/i.test(e?.message || ''));
64
+ const signoffPassed = signoffResult ? /pass|success/i.test(signoffResult) : null;
65
+ const lineCov = coverage?.line || coverage?.line_pct;
66
+ const branchCov = coverage?.branch || coverage?.branch_pct;
67
+
68
+ const totalHealActions =
69
+ (selfHeal.stage_exception_count || 0) +
70
+ (selfHeal.formal_regen_count || 0) +
71
+ (selfHeal.coverage_best_restore_count || 0) +
72
+ (selfHeal.coverage_regression_reject_count || 0) +
73
+ (selfHeal.deterministic_tb_fallback_count || 0);
74
+
75
+ const hasMetrics = metrics.wns !== undefined || metrics.area || metrics.power || metrics.gate_count;
76
+ const failureExplanation = result?.failure_explanation;
77
+ const failureSuggestion = result?.failure_suggestion;
78
+
79
+ return (
80
+ <div className="cs-root">
81
+
82
+ {/* ── Banner ─────────────────────────────── */}
83
+ <motion.div
84
+ className={`cs-banner ${success ? 'cs-banner--success' : 'cs-banner--fail'}`}
85
+ initial={{ opacity: 0, y: -8 }}
86
+ animate={{ opacity: 1, y: 0 }}
87
+ transition={{ duration: 0.35 }}
88
+ >
89
+ <div className="cs-banner-dot" />
90
+ <div className="cs-banner-body">
91
+ <h1 className="cs-banner-title">
92
+ {success ? `${designName} is ready` : 'Build stopped'}
93
+ </h1>
94
+ <p className="cs-banner-sub">
95
+ {success
96
+ ? [
97
+ buildTimeMin > 0 ? `${buildTimeMin} min` : null,
98
+ checkpointCount > 0 ? `${checkpointCount} checkpoints passed` : null,
99
+ strategy || null,
100
+ ].filter(Boolean).join(' Β· ')
101
+ : `${designName} Β· Review the log below for details`
102
+ }
103
+ </p>
104
+ </div>
105
+ </motion.div>
106
+
107
+ {/* ── Silicon Metrics ─────────────────────── */}
108
+ {success && hasMetrics && (
109
+ <div className="cs-section">
110
+ <h2 className="cs-section-title">Silicon Metrics</h2>
111
+ <div className="cs-stats-row">
112
+ <StatCell
113
+ label="Worst negative slack"
114
+ value={metrics.wns !== undefined ? `${metrics.wns} ns` : null}
115
+ warn={metrics.wns !== undefined && metrics.wns < 0}
116
+ />
117
+ <div className="cs-stat-divider" />
118
+ <StatCell label="Die area" value={metrics.area} />
119
+ <div className="cs-stat-divider" />
120
+ <StatCell label="Total power" value={metrics.power} />
121
+ <div className="cs-stat-divider" />
122
+ <StatCell label="Gate count" value={metrics.gate_count} />
123
+ </div>
124
+ </div>
125
+ )}
126
+
127
+ {/* ── Verification ────────────────────────── */}
128
+ <div className="cs-section">
129
+ <h2 className="cs-section-title">Verification</h2>
130
+ <div className="cs-checks">
131
+ <CheckRow
132
+ label="RTL syntax & lint"
133
+ passed={syntaxPassed || null}
134
+ detail={syntaxPassed ? 'Clean β€” Verilator lint-only' : undefined}
135
+ />
136
+ <CheckRow
137
+ label="Functional simulation"
138
+ passed={simPassed || null}
139
+ detail={simPassed ? 'TEST PASSED' : undefined}
140
+ />
141
+ <CheckRow
142
+ label="Formal property verification"
143
+ passed={formalPassed}
144
+ detail={formalResult ? formalResult.substring(0, 60) : undefined}
145
+ />
146
+ <CheckRow
147
+ label="Coverage closure"
148
+ passed={coveragePassed || null}
149
+ detail={lineCov ? `Line ${lineCov}%${branchCov ? ` Β· Branch ${branchCov}%` : ''}` : undefined}
150
+ />
151
+ <CheckRow
152
+ label="DRC / LVS signoff"
153
+ passed={signoffPassed}
154
+ detail={signoffResult ? signoffResult.substring(0, 60) : (success ? 'Physical checks completed' : undefined)}
155
+ />
156
+ </div>
157
+ </div>
158
+
159
+ {/* ── Self-Healing ────────────────────────── */}
160
+ {totalHealActions > 0 && (
161
+ <div className="cs-section">
162
+ <h2 className="cs-section-title">Self-Healing Activity</h2>
163
+ <div className="cs-heal-list">
164
+ {selfHeal.stage_exception_count > 0 && (
165
+ <div className="cs-heal-item">
166
+ Stage exception guards
167
+ <span className="cs-heal-count">{selfHeal.stage_exception_count}Γ—</span>
168
+ </div>
169
+ )}
170
+ {selfHeal.formal_regen_count > 0 && (
171
+ <div className="cs-heal-item">
172
+ Formal SVA regeneration
173
+ <span className="cs-heal-count">{selfHeal.formal_regen_count}Γ—</span>
174
+ </div>
175
+ )}
176
+ {selfHeal.coverage_regression_reject_count > 0 && (
177
+ <div className="cs-heal-item">
178
+ Testbench regression rejections
179
+ <span className="cs-heal-count">{selfHeal.coverage_regression_reject_count}Γ—</span>
180
+ </div>
181
+ )}
182
+ {selfHeal.coverage_best_restore_count > 0 && (
183
+ <div className="cs-heal-item">
184
+ Best testbench restores
185
+ <span className="cs-heal-count">{selfHeal.coverage_best_restore_count}Γ—</span>
186
+ </div>
187
+ )}
188
+ {selfHeal.deterministic_tb_fallback_count > 0 && (
189
+ <div className="cs-heal-item">
190
+ Deterministic testbench fallbacks
191
+ <span className="cs-heal-count">{selfHeal.deterministic_tb_fallback_count}Γ—</span>
192
+ </div>
193
+ )}
194
+ </div>
195
+ </div>
196
+ )}
197
+
198
+ {/* ── Architecture Spec ───────────────────── */}
199
+ {spec && (
200
+ <div className="cs-section">
201
+ <h2 className="cs-section-title">Architecture Specification</h2>
202
+ <div className="cs-code-block">
203
+ <pre className="cs-pre cs-pre--prose">
204
+ {spec.substring(0, 1200)}{spec.length > 1200 ? '\n…' : ''}
205
+ </pre>
206
+ </div>
207
+ </div>
208
+ )}
209
+
210
+ {/* ── RTL Preview ─────────────────────────── */}
211
+ {rtlSnippet && (
212
+ <div className="cs-section">
213
+ <h2 className="cs-section-title">RTL Preview</h2>
214
+ <div className="cs-code-block cs-code-block--dark">
215
+ <pre className="cs-pre cs-pre--code">
216
+ {rtlSnippet.substring(0, 1200)}{rtlSnippet.length > 1200 ? '\n// …' : ''}
217
+ </pre>
218
+ </div>
219
+ </div>
220
+ )}
221
+
222
+ {/* ── Convergence History ─────────────────── */}
223
+ {convergence.length > 0 && (
224
+ <div className="cs-section">
225
+ <h2 className="cs-section-title">Convergence History</h2>
226
+ <div className="cs-table-wrap">
227
+ <table className="cs-table">
228
+ <thead>
229
+ <tr>
230
+ <th>Iter</th><th>WNS (ns)</th><th>TNS (ns)</th>
231
+ <th>Congestion %</th><th>Area (Β΅mΒ²)</th><th>Power (W)</th>
232
+ </tr>
233
+ </thead>
234
+ <tbody>
235
+ {convergence.slice(-5).map((s: any, i: number) => (
236
+ <tr key={i}>
237
+ <td>{s.iteration}</td>
238
+ <td data-good={s.wns >= 0 ? 'true' : 'false'}>{s.wns?.toFixed(3)}</td>
239
+ <td>{s.tns?.toFixed(3)}</td>
240
+ <td>{s.congestion?.toFixed(2)}</td>
241
+ <td>{s.area_um2?.toFixed(0)}</td>
242
+ <td>{s.power_w?.toExponential(2)}</td>
243
+ </tr>
244
+ ))}
245
+ </tbody>
246
+ </table>
247
+ </div>
248
+ </div>
249
+ )}
250
+
251
+ {/* ── Failure Detail ──────────────────────── */}
252
+ {!success && (failureExplanation || result?.error) && (
253
+ <div className="cs-section">
254
+ <h2 className="cs-section-title">What went wrong</h2>
255
+ {failureExplanation && (
256
+ <p className="cs-fail-explanation">{failureExplanation}</p>
257
+ )}
258
+ {failureSuggestion && (
259
+ <p className="cs-fail-suggestion">
260
+ <span className="cs-fail-suggestion-label">Try:</span>
261
+ {failureSuggestion}
262
+ </p>
263
+ )}
264
+ {result?.error && !failureExplanation && (
265
+ <div className="cs-code-block">
266
+ <pre className="cs-pre cs-pre--error">
267
+ {String(result.error).substring(0, 500)}
268
+ </pre>
269
+ </div>
270
+ )}
271
+ </div>
272
+ )}
273
+
274
+ {/* ── Actions ─────────────────────────────── */}
275
+ <div className="cs-actions">
276
+ <motion.button
277
+ className="cs-reset-btn"
278
+ onClick={onReset}
279
+ whileHover={{ scale: 1.02 }}
280
+ whileTap={{ scale: 0.98 }}
281
+ >
282
+ Build another chip
283
+ </motion.button>
284
+ </div>
285
+ </div>
286
+ );
287
+ };
288
+
289
+ interface Props {
290
+ designName: string;
291
+ result: any;
292
+ jobStatus: string;
293
+ events: any[];
294
+ onReset: () => void;
295
+ }
296
+
297
  function MetricCard({ label, value, icon, color }: { label: string; value: any; icon: string; color: string }) {
298
  return (
299
  <motion.div
web/src/hitl.css CHANGED
@@ -1600,3 +1600,225 @@
1600
  text-align: center;
1601
  }
1602
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1600
  text-align: center;
1601
  }
1602
  }
1603
+
1604
+
1605
+ /* ════════════════════════════════════════════════════════════════════
1606
+ APPROVAL CARD (ac-*)
1607
+ Premium, minimal β€” stage checkpoint card with next-step context
1608
+ ════════════════════════════════════════════════════════════════════ */
1609
+
1610
+ .ac-card {
1611
+ position: relative;
1612
+ background: var(--bg-elevated);
1613
+ border: 1px solid var(--border);
1614
+ border-radius: 10px;
1615
+ padding: 1.125rem 1.25rem 1rem;
1616
+ display: flex;
1617
+ flex-direction: column;
1618
+ gap: 0.625rem;
1619
+ padding-left: 1.5rem; /* space for accent bar */
1620
+ }
1621
+
1622
+ .ac-card::before {
1623
+ content: '';
1624
+ position: absolute;
1625
+ left: 0;
1626
+ top: 12%;
1627
+ bottom: 12%;
1628
+ width: 3px;
1629
+ border-radius: 0 2px 2px 0;
1630
+ transition: background 200ms;
1631
+ }
1632
+
1633
+ .ac-card--ok::before { background: var(--green); }
1634
+ .ac-card--warn::before { background: var(--amber); }
1635
+ .ac-card--error::before { background: var(--red); }
1636
+
1637
+ /* Header */
1638
+ .ac-header {
1639
+ display: flex;
1640
+ align-items: center;
1641
+ justify-content: space-between;
1642
+ gap: 0.75rem;
1643
+ }
1644
+
1645
+ .ac-stage-id {
1646
+ display: flex;
1647
+ align-items: center;
1648
+ gap: 0.375rem;
1649
+ }
1650
+
1651
+ .ac-stage-symbol {
1652
+ font-size: 0.6875rem;
1653
+ color: var(--text-tertiary);
1654
+ line-height: 1;
1655
+ }
1656
+
1657
+ .ac-stage-label {
1658
+ font-size: 0.6875rem;
1659
+ font-weight: 600;
1660
+ text-transform: uppercase;
1661
+ letter-spacing: 0.09em;
1662
+ color: var(--text-secondary);
1663
+ }
1664
+
1665
+ .ac-badge {
1666
+ font-size: 0.625rem;
1667
+ font-weight: 600;
1668
+ letter-spacing: 0.04em;
1669
+ text-transform: uppercase;
1670
+ padding: 0.125rem 0.4375rem;
1671
+ border-radius: 4px;
1672
+ }
1673
+
1674
+ .ac-badge--warn { background: rgba(212,124,58,0.1); color: var(--amber); }
1675
+ .ac-badge--error { background: rgba(192,57,43,0.1); color: var(--red); }
1676
+
1677
+ .ac-artifact-pill {
1678
+ font-size: 0.6875rem;
1679
+ color: var(--text-tertiary);
1680
+ font-family: 'JetBrains Mono', monospace;
1681
+ white-space: nowrap;
1682
+ }
1683
+
1684
+ /* Summary */
1685
+ .ac-summary {
1686
+ font-size: 0.875rem;
1687
+ line-height: 1.65;
1688
+ color: var(--text-primary);
1689
+ margin: 0;
1690
+ }
1691
+
1692
+ /* Next stage hint */
1693
+ .ac-next-hint {
1694
+ display: flex;
1695
+ align-items: baseline;
1696
+ gap: 0.375rem;
1697
+ font-size: 0.775rem;
1698
+ color: var(--text-tertiary);
1699
+ padding: 0.5rem 0 0.125rem;
1700
+ border-top: 1px solid var(--border-subtle);
1701
+ }
1702
+
1703
+ .ac-next-arrow {
1704
+ font-size: 0.75rem;
1705
+ opacity: 0.5;
1706
+ flex-shrink: 0;
1707
+ }
1708
+
1709
+ .ac-next-text {
1710
+ line-height: 1.45;
1711
+ }
1712
+
1713
+ /* Footer */
1714
+ .ac-footer {
1715
+ display: flex;
1716
+ align-items: center;
1717
+ justify-content: flex-end;
1718
+ gap: 0.625rem;
1719
+ padding-top: 0.5rem;
1720
+ border-top: 1px solid var(--border-subtle);
1721
+ }
1722
+
1723
+ .ac-give-feedback {
1724
+ background: none;
1725
+ border: none;
1726
+ font-family: inherit;
1727
+ font-size: 0.775rem;
1728
+ color: var(--text-tertiary);
1729
+ cursor: pointer;
1730
+ padding: 0;
1731
+ text-decoration: underline;
1732
+ text-underline-offset: 2px;
1733
+ transition: color 120ms;
1734
+ margin-right: auto;
1735
+ }
1736
+
1737
+ .ac-give-feedback:hover { color: var(--text-secondary); }
1738
+
1739
+ .ac-continue-btn {
1740
+ display: inline-flex;
1741
+ align-items: center;
1742
+ gap: 0.3rem;
1743
+ background: var(--green);
1744
+ color: #fff;
1745
+ border: none;
1746
+ font-family: inherit;
1747
+ font-size: 0.8125rem;
1748
+ font-weight: 500;
1749
+ padding: 0.4375rem 1rem;
1750
+ border-radius: 6px;
1751
+ cursor: pointer;
1752
+ transition: opacity 150ms, transform 150ms;
1753
+ letter-spacing: 0.01em;
1754
+ }
1755
+
1756
+ .ac-continue-btn:hover:not(:disabled) {
1757
+ opacity: 0.88;
1758
+ transform: translateY(-1px);
1759
+ }
1760
+
1761
+ .ac-continue-btn:disabled {
1762
+ opacity: 0.5;
1763
+ cursor: not-allowed;
1764
+ }
1765
+
1766
+ .ac-chevron {
1767
+ font-size: 0.9rem;
1768
+ line-height: 1;
1769
+ opacity: 0.85;
1770
+ }
1771
+
1772
+ /* Feedback row */
1773
+ .ac-feedback-row {
1774
+ display: flex;
1775
+ align-items: center;
1776
+ gap: 0.5rem;
1777
+ width: 100%;
1778
+ }
1779
+
1780
+ .ac-feedback-input {
1781
+ flex: 1;
1782
+ background: var(--bg);
1783
+ border: 1px solid var(--border);
1784
+ border-radius: 6px;
1785
+ padding: 0.375rem 0.625rem;
1786
+ font-family: inherit;
1787
+ font-size: 0.8125rem;
1788
+ color: var(--text-primary);
1789
+ outline: none;
1790
+ transition: border-color 150ms;
1791
+ }
1792
+
1793
+ .ac-feedback-input:focus { border-color: var(--text-secondary); }
1794
+ .ac-feedback-input::placeholder { color: var(--text-tertiary); }
1795
+
1796
+ .ac-reject-btn {
1797
+ background: transparent;
1798
+ border: 1px solid rgba(192,57,43,0.3);
1799
+ color: var(--red);
1800
+ font-family: inherit;
1801
+ font-size: 0.775rem;
1802
+ font-weight: 500;
1803
+ padding: 0.375rem 0.75rem;
1804
+ border-radius: 6px;
1805
+ cursor: pointer;
1806
+ white-space: nowrap;
1807
+ transition: all 120ms;
1808
+ }
1809
+
1810
+ .ac-reject-btn:hover:not(:disabled) { background: rgba(192,57,43,0.05); border-color: var(--red); }
1811
+ .ac-reject-btn:disabled { opacity: 0.5; cursor: not-allowed; }
1812
+
1813
+ .ac-cancel-btn {
1814
+ background: none;
1815
+ border: none;
1816
+ font-family: inherit;
1817
+ font-size: 0.775rem;
1818
+ color: var(--text-tertiary);
1819
+ cursor: pointer;
1820
+ padding: 0.375rem 0.25rem;
1821
+ transition: color 120ms;
1822
+ }
1823
+
1824
+ .ac-cancel-btn:hover { color: var(--text-secondary); }
web/src/index.css CHANGED
@@ -2631,3 +2631,351 @@ body {
2631
  }
2632
  }
2633
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2631
  }
2632
  }
2633
 
2634
+
2635
+ /* ════════════════════════════════════════════════════════════════════
2636
+ CHIP SUMMARY (cs-*)
2637
+ Premium result screen β€” clean, editorial, minimal
2638
+ ════════════════════════════════════════════════════════════════════ */
2639
+
2640
+ .cs-root {
2641
+ padding: 2rem 2.25rem;
2642
+ display: flex;
2643
+ flex-direction: column;
2644
+ gap: 2rem;
2645
+ max-width: 860px;
2646
+ margin: 0 auto;
2647
+ }
2648
+
2649
+ /* ── Banner ─────────────────────────────────────── */
2650
+ .cs-banner {
2651
+ display: flex;
2652
+ align-items: flex-start;
2653
+ gap: 1rem;
2654
+ padding: 1.25rem 1.5rem;
2655
+ border-radius: 12px;
2656
+ border: 1px solid var(--border);
2657
+ background: var(--bg-card);
2658
+ }
2659
+
2660
+ .cs-banner--success {
2661
+ border-color: rgba(74, 124, 89, 0.28);
2662
+ background: rgba(74, 124, 89, 0.04);
2663
+ }
2664
+
2665
+ .cs-banner--fail {
2666
+ border-color: rgba(192, 57, 43, 0.22);
2667
+ background: rgba(192, 57, 43, 0.03);
2668
+ }
2669
+
2670
+ .cs-banner-dot {
2671
+ width: 8px;
2672
+ height: 8px;
2673
+ border-radius: 50%;
2674
+ flex-shrink: 0;
2675
+ margin-top: 0.45rem;
2676
+ }
2677
+
2678
+ .cs-banner--success .cs-banner-dot {
2679
+ background: #4a7c59;
2680
+ box-shadow: 0 0 0 3px rgba(74, 124, 89, 0.15);
2681
+ }
2682
+
2683
+ .cs-banner--fail .cs-banner-dot {
2684
+ background: #c0392b;
2685
+ box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.12);
2686
+ }
2687
+
2688
+ .cs-banner-body { flex: 1; min-width: 0; }
2689
+
2690
+ .cs-banner-title {
2691
+ font-size: 1.1875rem;
2692
+ font-weight: 600;
2693
+ color: var(--text);
2694
+ margin: 0 0 0.3rem;
2695
+ line-height: 1.3;
2696
+ }
2697
+
2698
+ .cs-banner-sub {
2699
+ font-size: 0.8125rem;
2700
+ color: var(--text-mid);
2701
+ margin: 0;
2702
+ line-height: 1.5;
2703
+ }
2704
+
2705
+ /* ── Section ─────────────────────────────────────── */
2706
+ .cs-section {
2707
+ display: flex;
2708
+ flex-direction: column;
2709
+ gap: 0.75rem;
2710
+ }
2711
+
2712
+ .cs-section-title {
2713
+ font-size: 0.6875rem;
2714
+ font-weight: 700;
2715
+ text-transform: uppercase;
2716
+ letter-spacing: 0.1em;
2717
+ color: var(--text-dim);
2718
+ margin: 0;
2719
+ padding-bottom: 0.625rem;
2720
+ border-bottom: 1px solid var(--border);
2721
+ }
2722
+
2723
+ /* ── Stats Row ───────────────────────────────────── */
2724
+ .cs-stats-row {
2725
+ display: flex;
2726
+ align-items: stretch;
2727
+ background: var(--bg-card);
2728
+ border: 1px solid var(--border);
2729
+ border-radius: 10px;
2730
+ overflow: hidden;
2731
+ }
2732
+
2733
+ .cs-stat {
2734
+ flex: 1;
2735
+ display: flex;
2736
+ flex-direction: column;
2737
+ gap: 0.3rem;
2738
+ padding: 1rem 1.25rem;
2739
+ }
2740
+
2741
+ .cs-stat-divider {
2742
+ width: 1px;
2743
+ background: var(--border);
2744
+ flex-shrink: 0;
2745
+ margin: 0.75rem 0;
2746
+ }
2747
+
2748
+ .cs-stat-value {
2749
+ font-size: 1.0625rem;
2750
+ font-weight: 600;
2751
+ color: var(--text);
2752
+ font-family: 'Fira Code', 'JetBrains Mono', monospace;
2753
+ line-height: 1.2;
2754
+ }
2755
+
2756
+ .cs-stat-value--warn { color: var(--warn); }
2757
+
2758
+ .cs-stat-label {
2759
+ font-size: 0.7rem;
2760
+ color: var(--text-dim);
2761
+ font-weight: 500;
2762
+ line-height: 1.3;
2763
+ }
2764
+
2765
+ /* ── Verification Checks ─────────────────────────── */
2766
+ .cs-checks {
2767
+ display: flex;
2768
+ flex-direction: column;
2769
+ border: 1px solid var(--border);
2770
+ border-radius: 10px;
2771
+ overflow: hidden;
2772
+ background: var(--bg-card);
2773
+ }
2774
+
2775
+ .cs-check-row {
2776
+ display: flex;
2777
+ align-items: center;
2778
+ gap: 0.75rem;
2779
+ padding: 0.7rem 1rem;
2780
+ border-bottom: 1px solid var(--border);
2781
+ transition: background 120ms;
2782
+ }
2783
+
2784
+ .cs-check-row:last-child { border-bottom: none; }
2785
+ .cs-check-row:hover { background: var(--bg-hover); }
2786
+
2787
+ .cs-check-icon {
2788
+ width: 20px;
2789
+ height: 20px;
2790
+ border-radius: 50%;
2791
+ display: flex;
2792
+ align-items: center;
2793
+ justify-content: center;
2794
+ font-size: 0.625rem;
2795
+ font-weight: 800;
2796
+ flex-shrink: 0;
2797
+ }
2798
+
2799
+ .cs-check-icon--pass { background: rgba(74,124,89,0.1); color: #4a7c59; border: 1px solid rgba(74,124,89,0.2); }
2800
+ .cs-check-icon--fail { background: rgba(192,57,43,0.08); color: #c0392b; border: 1px solid rgba(192,57,43,0.15); }
2801
+ .cs-check-icon--skip { background: rgba(0,0,0,0.03); color: var(--text-dim); border: 1px solid var(--border); }
2802
+
2803
+ .cs-check-body { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
2804
+
2805
+ .cs-check-label {
2806
+ font-size: 0.8375rem;
2807
+ font-weight: 500;
2808
+ color: var(--text);
2809
+ }
2810
+
2811
+ .cs-check-detail {
2812
+ font-size: 0.725rem;
2813
+ color: var(--text-dim);
2814
+ font-family: 'Fira Code', monospace;
2815
+ }
2816
+
2817
+ /* ── Self-Healing ────────────────────────────────── */
2818
+ .cs-heal-list {
2819
+ display: flex;
2820
+ flex-direction: column;
2821
+ border: 1px solid var(--border);
2822
+ border-radius: 8px;
2823
+ overflow: hidden;
2824
+ }
2825
+
2826
+ .cs-heal-item {
2827
+ display: flex;
2828
+ align-items: center;
2829
+ justify-content: space-between;
2830
+ padding: 0.575rem 0.875rem;
2831
+ font-size: 0.8125rem;
2832
+ color: var(--text-mid);
2833
+ border-bottom: 1px solid var(--border);
2834
+ }
2835
+
2836
+ .cs-heal-item:last-child { border-bottom: none; }
2837
+
2838
+ .cs-heal-count {
2839
+ font-family: 'Fira Code', monospace;
2840
+ font-size: 0.75rem;
2841
+ color: var(--text-dim);
2842
+ font-weight: 600;
2843
+ }
2844
+
2845
+ /* ── Code Blocks ─────────────────────────────────── */
2846
+ .cs-code-block {
2847
+ border: 1px solid var(--border);
2848
+ border-radius: 8px;
2849
+ overflow: hidden;
2850
+ }
2851
+
2852
+ .cs-code-block--dark {
2853
+ background: #1a1917;
2854
+ border-color: #2d2b28;
2855
+ }
2856
+
2857
+ .cs-pre {
2858
+ margin: 0;
2859
+ padding: 1rem 1.125rem;
2860
+ overflow: auto;
2861
+ max-height: 220px;
2862
+ font-size: 0.775rem;
2863
+ line-height: 1.6;
2864
+ }
2865
+
2866
+ .cs-pre--prose {
2867
+ font-family: inherit;
2868
+ color: var(--text);
2869
+ background: var(--bg);
2870
+ white-space: pre-wrap;
2871
+ }
2872
+
2873
+ .cs-pre--code {
2874
+ font-family: 'Fira Code', 'JetBrains Mono', monospace;
2875
+ color: #e2ddd8;
2876
+ background: #1a1917;
2877
+ white-space: pre;
2878
+ }
2879
+
2880
+ .cs-pre--error {
2881
+ font-family: 'Fira Code', monospace;
2882
+ color: #c0392b;
2883
+ background: rgba(192, 57, 43, 0.04);
2884
+ white-space: pre-wrap;
2885
+ }
2886
+
2887
+ /* ── Convergence Table ───────────────────────────── */
2888
+ .cs-table-wrap {
2889
+ border: 1px solid var(--border);
2890
+ border-radius: 8px;
2891
+ overflow: auto;
2892
+ }
2893
+
2894
+ .cs-table {
2895
+ width: 100%;
2896
+ border-collapse: collapse;
2897
+ font-size: 0.775rem;
2898
+ }
2899
+
2900
+ .cs-table th {
2901
+ background: var(--bg);
2902
+ color: var(--text-dim);
2903
+ padding: 0.5rem 0.875rem;
2904
+ text-align: left;
2905
+ border-bottom: 1px solid var(--border);
2906
+ font-size: 0.6875rem;
2907
+ font-weight: 700;
2908
+ text-transform: uppercase;
2909
+ letter-spacing: 0.08em;
2910
+ }
2911
+
2912
+ .cs-table td {
2913
+ color: var(--text);
2914
+ padding: 0.4375rem 0.875rem;
2915
+ border-bottom: 1px solid var(--border);
2916
+ font-family: 'Fira Code', monospace;
2917
+ }
2918
+
2919
+ .cs-table tr:last-child td { border-bottom: none; }
2920
+ .cs-table tbody tr:hover td { background: var(--bg-hover); }
2921
+ .cs-table td[data-good="false"] { color: #c0392b; }
2922
+ .cs-table td[data-good="true"] { color: #4a7c59; }
2923
+
2924
+ /* ── Failure Detail ──────────────────────────────── */
2925
+ .cs-fail-explanation {
2926
+ font-size: 0.875rem;
2927
+ line-height: 1.65;
2928
+ color: var(--text);
2929
+ margin: 0 0 0.625rem;
2930
+ }
2931
+
2932
+ .cs-fail-suggestion {
2933
+ font-size: 0.8125rem;
2934
+ line-height: 1.5;
2935
+ color: var(--text-mid);
2936
+ margin: 0;
2937
+ display: flex;
2938
+ gap: 0.375rem;
2939
+ }
2940
+
2941
+ .cs-fail-suggestion-label {
2942
+ font-weight: 600;
2943
+ color: var(--text-dim);
2944
+ flex-shrink: 0;
2945
+ }
2946
+
2947
+ /* ── Actions ─────────────────────────────────────── */
2948
+ .cs-actions {
2949
+ display: flex;
2950
+ justify-content: center;
2951
+ padding: 0.5rem 0 2.5rem;
2952
+ }
2953
+
2954
+ .cs-reset-btn {
2955
+ background: var(--accent);
2956
+ color: #fff;
2957
+ border: none;
2958
+ font-family: inherit;
2959
+ font-size: 0.875rem;
2960
+ font-weight: 500;
2961
+ padding: 0.625rem 1.75rem;
2962
+ border-radius: 8px;
2963
+ cursor: pointer;
2964
+ letter-spacing: 0.01em;
2965
+ box-shadow: 0 1px 4px rgba(201, 100, 62, 0.2);
2966
+ transition: opacity 150ms, transform 150ms, box-shadow 150ms;
2967
+ }
2968
+
2969
+ .cs-reset-btn:hover {
2970
+ opacity: 0.9;
2971
+ transform: translateY(-1px);
2972
+ box-shadow: 0 3px 10px rgba(201, 100, 62, 0.28);
2973
+ }
2974
+
2975
+ /* ── Responsive ─────────────────────────────────── */
2976
+ @media (max-width: 640px) {
2977
+ .cs-root { padding: 1.25rem 1rem; gap: 1.5rem; }
2978
+ .cs-stats-row { flex-direction: column; }
2979
+ .cs-stat-divider { width: auto; height: 1px; margin: 0; }
2980
+ }
2981
+