11import {
2+ Bot ,
23 History ,
4+ Inbox ,
35 ListChecks ,
46 ScrollText ,
57 Wand2 ,
68} from "lucide-react" ;
79import { Button , Card , CardContent , CardHeader , CardTitle } from "./sdk" ;
810import { Spinner } from "./Spinner" ;
9- import {
10- countLabel ,
11- formatHistoryTime ,
12- } from "./curation/format" ;
11+ import { countLabel , formatHistoryTime } from "./curation/format" ;
1312import { groupActions } from "./curation/risk" ;
1413import { ActivityScroller } from "./curation/ActivityScroller" ;
1514import { ActionReviewGroup } from "./curation/ActionReviewGroup" ;
16- import {
17- useCurationData ,
18- type CurationTab ,
19- } from "./curation/useCurationData" ;
15+ import { useCurationData , type CurationTab } from "./curation/useCurationData" ;
16+ import { usePendingAutomationCounts } from "./curation/usePendingAutomationCounts" ;
17+ import { api } from "./api" ;
18+ import { CurationAutomationPanel } from "./curation/CurationAutomationPanel" ;
19+ import { CurationProposalsPanel } from "./curation/CurationProposalsPanel" ;
2020import { CurationHistoryPanel } from "./curation/CurationHistoryPanel" ;
2121import { InlineConfirm } from "./curation/InlineConfirm" ;
22- import { isActiveAutomationStatus , type AutomationRunTask } from "./curation/automationTasks" ;
22+ import {
23+ isActiveAutomationStatus ,
24+ type AutomationRunTask ,
25+ } from "./curation/automationTasks" ;
2326import type { SecondsField , TaskField } from "./curation/configTypes" ;
2427
2528const DIAGNOSTIC_COUNT_KEYS = new Set ( [
@@ -118,15 +121,19 @@ export default function CurationPanel({
118121 const actions = report ?. actions ?? [ ] ;
119122 const counts : Record < string , number > = report ?. counts ?? { } ;
120123 const isPlan = report ?. dry_run ?? true ;
121- const shownCounts : Record < string , number > = isPlan ? counts : ( report ?. applied_counts ?? counts ) ;
124+ const shownCounts : Record < string , number > = isPlan
125+ ? counts
126+ : ( report ?. applied_counts ?? counts ) ;
122127 const actionCounts = Object . entries ( shownCounts ) . filter (
123128 ( [ key ] ) => ! DIAGNOSTIC_COUNT_KEYS . has ( key ) ,
124129 ) ;
125130 const diagnosticCounts = Object . entries ( counts ) . filter ( ( [ key ] ) =>
126131 DIAGNOSTIC_COUNT_KEYS . has ( key ) ,
127132 ) ;
128133 const actionGroups = groupActions ( actions ) ;
129- const nonEmptyActionGroups = actionGroups . filter ( ( group ) => group . actions . length > 0 ) ;
134+ const nonEmptyActionGroups = actionGroups . filter (
135+ ( group ) => group . actions . length > 0 ,
136+ ) ;
130137 const backendAvailability = configResponse ?. backend_availability ;
131138 const backendUnavailable =
132139 ! configDirty &&
@@ -145,7 +152,8 @@ export default function CurationPanel({
145152 ! automationRunActioning ;
146153 const activeAutomationStatus = ( task : AutomationRunTask ) =>
147154 automationRuns . find (
148- ( record ) => record . task === task && isActiveAutomationStatus ( record . status ) ,
155+ ( record ) =>
156+ record . task === task && isActiveAutomationStatus ( record . status ) ,
149157 ) ?. status ;
150158 const automationRunTitle = configDirty
151159 ? "Save automation config before running"
@@ -181,15 +189,12 @@ export default function CurationPanel({
181189 [ key ] : value ? Math . max ( 1 , Number ( value ) || 1 ) : null ,
182190 } ) ;
183191 } ;
184- const taskFieldError = (
185- task : AutomationRunTask ,
186- field : TaskField ,
187- ) => configFieldErrors [ `${ task } .${ field } ` ] ;
192+ const taskFieldError = ( task : AutomationRunTask , field : TaskField ) =>
193+ configFieldErrors [ `${ task } .${ field } ` ] ;
188194 const planLabel = actions . length ? `Plan ${ actions . length } ` : "Plan" ;
189- const confirmGroupCounts = nonEmptyActionGroups . map ( ( group ) => [
190- group . label ,
191- group . actions . length ,
192- ] as const ) ;
195+ const confirmGroupCounts = nonEmptyActionGroups . map (
196+ ( group ) => [ group . label , group . actions . length ] as const ,
197+ ) ;
193198 const selectedUsage = selectedManagedSkill
194199 ? managedSkillUsage [ selectedManagedSkill . metadata . id ]
195200 : null ;
@@ -199,8 +204,16 @@ export default function CurationPanel({
199204 const selectedImprovementRecommendation = selectedManagedSkill
200205 ? managedSkillImprovementRecommendations [ selectedManagedSkill . metadata . id ]
201206 : null ;
207+ // Same server-side count the outer Curation tab badge uses, so the two
208+ // badges can't disagree and no proposal/skill lists need loading up front.
209+ const pendingProposalCount = usePendingAutomationCounts ( api ) ;
210+ const proposalsLabel = pendingProposalCount
211+ ? `Proposals ${ pendingProposalCount } `
212+ : "Proposals" ;
202213 const tabs : Array < { id : CurationTab ; label : string ; Icon : typeof Wand2 } > = [
203214 { id : "plan" , label : planLabel , Icon : ListChecks } ,
215+ { id : "automation" , label : "Automation" , Icon : Bot } ,
216+ { id : "proposals" , label : proposalsLabel , Icon : Inbox } ,
204217 { id : "history" , label : "History" , Icon : History } ,
205218 { id : "activity" , label : "Activity" , Icon : ScrollText } ,
206219 ] ;
@@ -213,7 +226,13 @@ export default function CurationPanel({
213226 Curation
214227 </ CardTitle >
215228 < div className = "flex items-center gap-2 shrink-0" >
216- < Button size = "sm" ghost disabled = { loading } onClick = { preview } className = "gap-2" >
229+ < Button
230+ size = "sm"
231+ ghost
232+ disabled = { loading }
233+ onClick = { preview }
234+ className = "gap-2"
235+ >
217236 { loading ? < Spinner /> : null }
218237 Preview
219238 </ Button >
@@ -245,7 +264,7 @@ export default function CurationPanel({
245264 ref = { panelRef }
246265 role = "tablist"
247266 aria-label = "Curation views"
248- className = "grid grid-cols-3 gap-1 rounded-sm border border-border bg-secondary/30 p-1 shrink-0"
267+ className = "grid grid-cols-5 gap-1 rounded-sm border border-border bg-secondary/30 p-1 shrink-0"
249268 >
250269 { tabs . map ( ( { id, label, Icon } ) => {
251270 const active = activeTab === id ;
@@ -286,7 +305,8 @@ export default function CurationPanel({
286305 ) }
287306 { previewStale ? (
288307 < div className = "border border-warning/30 bg-warning/10 px-3 py-2 text-xs text-warning shrink-0" >
289- { previewStaleReason || "This saved preview is stale because the memory store changed." }
308+ { previewStaleReason ||
309+ "This saved preview is stale because the memory store changed." }
290310 </ div >
291311 ) : null }
292312
@@ -306,24 +326,31 @@ export default function CurationPanel({
306326 < >
307327 < span className = "text-text-tertiary" > · signals</ span >
308328 { diagnosticCounts . map ( ( [ k , v ] ) => (
309- < span key = { k } className = "font-mono-ui whitespace-nowrap text-text-tertiary" >
329+ < span
330+ key = { k }
331+ className = "font-mono-ui whitespace-nowrap text-text-tertiary"
332+ >
310333 { countLabel ( k ) } ={ v }
311334 </ span >
312335 ) ) }
313336 </ >
314337 ) : null }
315- < span className = "text-text-tertiary whitespace-nowrap" > · llm_calls={ report . llm_calls } </ span >
338+ < span className = "text-text-tertiary whitespace-nowrap" >
339+ · llm_calls={ report . llm_calls }
340+ </ span >
316341 { report . coverage ? (
317342 < span className = "text-text-tertiary whitespace-nowrap" >
318- · scanned={ report . coverage . scanned } /{ report . coverage . active_total }
343+ · scanned={ report . coverage . scanned } /
344+ { report . coverage . active_total }
319345 { report . coverage . due_remaining
320346 ? ` · due=${ report . coverage . due_remaining } `
321347 : "" }
322348 </ span >
323349 ) : null }
324350 { report . coverage ?. entity_total != null ? (
325351 < span className = "text-text-tertiary whitespace-nowrap" >
326- · entities={ report . coverage . entities_scanned ?? 0 } /{ report . coverage . entity_total }
352+ · entities={ report . coverage . entities_scanned ?? 0 } /
353+ { report . coverage . entity_total }
327354 { report . coverage . entity_scan_remaining
328355 ? ` · entity_due=${ report . coverage . entity_scan_remaining } `
329356 : "" }
@@ -335,7 +362,9 @@ export default function CurationPanel({
335362 </ span >
336363 ) : null }
337364 { ! isPlan && report . skipped_actions ? (
338- < span className = "text-warning whitespace-nowrap" > · skipped={ report . skipped_actions } </ span >
365+ < span className = "text-warning whitespace-nowrap" >
366+ · skipped={ report . skipped_actions }
367+ </ span >
339368 ) : null }
340369 </ div >
341370 ) }
@@ -348,7 +377,8 @@ export default function CurationPanel({
348377
349378 { ! report && ! loading && (
350379 < p className = "text-xs text-text-tertiary shrink-0" >
351- Click < span className = "text-text-secondary" > Preview</ span > to see proposed maintenance actions.
380+ Click < span className = "text-text-secondary" > Preview</ span > to
381+ see proposed maintenance actions.
352382 </ p >
353383 ) }
354384
@@ -407,21 +437,11 @@ export default function CurationPanel({
407437 </ div >
408438 ) : null }
409439
410- { activeTab === "history" ? (
411- < CurationHistoryPanel
412- report = { report }
413- previewSavedAt = { previewSavedAt }
414- previewStale = { previewStale }
415- previewStaleReason = { previewStaleReason }
416- actionsLength = { actions . length }
417- actionCounts = { actionCounts }
418- diagnosticCounts = { diagnosticCounts }
419- isPlan = { isPlan }
440+ { activeTab === "automation" ? (
441+ < CurationAutomationPanel
420442 status = { status }
421443 statusLoading = { statusLoading }
422444 statusError = { statusError }
423- oplog = { oplog }
424- oplogError = { oplogError }
425445 automationRuns = { automationRuns }
426446 automationRunsError = { automationRunsError }
427447 automationRunActioning = { automationRunActioning }
@@ -430,20 +450,6 @@ export default function CurationPanel({
430450 automationRunArtifact = { automationRunArtifact }
431451 automationRunArtifactLoading = { automationRunArtifactLoading }
432452 automationRunArtifactError = { automationRunArtifactError }
433- factProposals = { factProposals }
434- factProposalsLoading = { factProposalsLoading }
435- factProposalsError = { factProposalsError }
436- factProposalActioning = { factProposalActioning }
437- managedSkills = { managedSkills }
438- selectedManagedSkillId = { selectedManagedSkillId }
439- selectedManagedSkill = { selectedManagedSkill }
440- selectedUsage = { selectedUsage }
441- selectedRecommendation = { selectedRecommendation }
442- selectedImprovementRecommendation = { selectedImprovementRecommendation }
443- managedSkillsLoading = { managedSkillsLoading }
444- managedSkillsError = { managedSkillsError }
445- managedSkillActioning = { managedSkillActioning }
446- managedSkillExports = { managedSkillExports }
447453 configDraft = { configDraft }
448454 configLoading = { configLoading }
449455 configSaving = { configSaving }
@@ -463,16 +469,10 @@ export default function CurationPanel({
463469 automationTaskLabel = { automationTaskLabel }
464470 taskFieldError = { taskFieldError }
465471 loadStatus = { loadStatus }
466- loadOplog = { loadOplog }
467472 loadAutomationRuns = { loadAutomationRuns }
468473 loadSchedulerStatus = { loadSchedulerStatus }
469474 loadAutomationRunArtifact = { loadAutomationRunArtifact }
470- loadFactProposals = { loadFactProposals }
471- loadManagedSkills = { loadManagedSkills }
472- loadManagedSkill = { loadManagedSkill }
473475 runAutomationTask = { runAutomationTask }
474- runFactProposalAction = { runFactProposalAction }
475- runManagedSkillAction = { runManagedSkillAction }
476476 setSchedulerPaused = { setSchedulerPaused }
477477 updateConfigDraft = { updateConfigDraft }
478478 updateConfigTaskDraft = { updateConfigTaskDraft }
@@ -482,6 +482,52 @@ export default function CurationPanel({
482482 saveConfigDraft = { saveConfigDraft }
483483 />
484484 ) : null }
485+
486+ { activeTab === "proposals" ? (
487+ < CurationProposalsPanel
488+ factProposals = { factProposals }
489+ factProposalsLoading = { factProposalsLoading }
490+ factProposalsError = { factProposalsError }
491+ factProposalActioning = { factProposalActioning }
492+ managedSkills = { managedSkills }
493+ selectedManagedSkillId = { selectedManagedSkillId }
494+ selectedManagedSkill = { selectedManagedSkill }
495+ selectedUsage = { selectedUsage }
496+ selectedRecommendation = { selectedRecommendation }
497+ selectedImprovementRecommendation = {
498+ selectedImprovementRecommendation
499+ }
500+ managedSkillsLoading = { managedSkillsLoading }
501+ managedSkillsError = { managedSkillsError }
502+ managedSkillActioning = { managedSkillActioning }
503+ managedSkillExports = { managedSkillExports }
504+ loadFactProposals = { loadFactProposals }
505+ loadManagedSkills = { loadManagedSkills }
506+ loadManagedSkill = { loadManagedSkill }
507+ runFactProposalAction = { runFactProposalAction }
508+ runManagedSkillAction = { runManagedSkillAction }
509+ />
510+ ) : null }
511+
512+ { activeTab === "history" ? (
513+ < CurationHistoryPanel
514+ report = { report }
515+ previewSavedAt = { previewSavedAt }
516+ previewStale = { previewStale }
517+ previewStaleReason = { previewStaleReason }
518+ actionsLength = { actions . length }
519+ actionCounts = { actionCounts }
520+ diagnosticCounts = { diagnosticCounts }
521+ isPlan = { isPlan }
522+ status = { status }
523+ statusLoading = { statusLoading }
524+ statusError = { statusError }
525+ oplog = { oplog }
526+ oplogError = { oplogError }
527+ loadStatus = { loadStatus }
528+ loadOplog = { loadOplog }
529+ />
530+ ) : null }
485531 </ CardContent >
486532
487533 < InlineConfirm
@@ -500,9 +546,14 @@ export default function CurationPanel({
500546 ) : (
501547 < div className = "grid grid-cols-2 gap-x-3 gap-y-1" >
502548 { confirmGroupCounts . map ( ( [ label , count ] ) => (
503- < div key = { label } className = "flex items-center justify-between gap-2" >
549+ < div
550+ key = { label }
551+ className = "flex items-center justify-between gap-2"
552+ >
504553 < span className = "text-text-tertiary" > { label } </ span >
505- < span className = "font-mono-ui text-text-secondary" > { count } </ span >
554+ < span className = "font-mono-ui text-text-secondary" >
555+ { count }
556+ </ span >
506557 </ div >
507558 ) ) }
508559 </ div >
0 commit comments