Skip to content

Commit b255633

Browse files
Merge pull request #214 from ScriptedAlchemy/feat/dashboard-ux-improvements
[codex] feat(dashboard): restructure curation tabs and surface LCM payload GC
2 parents 31e421f + 2b9978b commit b255633

17 files changed

Lines changed: 2502 additions & 1172 deletions

dashboard/holographic/src/CurationPanel.tsx

Lines changed: 114 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import {
2+
Bot,
23
History,
4+
Inbox,
35
ListChecks,
46
ScrollText,
57
Wand2,
68
} from "lucide-react";
79
import { Button, Card, CardContent, CardHeader, CardTitle } from "./sdk";
810
import { Spinner } from "./Spinner";
9-
import {
10-
countLabel,
11-
formatHistoryTime,
12-
} from "./curation/format";
11+
import { countLabel, formatHistoryTime } from "./curation/format";
1312
import { groupActions } from "./curation/risk";
1413
import { ActivityScroller } from "./curation/ActivityScroller";
1514
import { 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";
2020
import { CurationHistoryPanel } from "./curation/CurationHistoryPanel";
2121
import { InlineConfirm } from "./curation/InlineConfirm";
22-
import { isActiveAutomationStatus, type AutomationRunTask } from "./curation/automationTasks";
22+
import {
23+
isActiveAutomationStatus,
24+
type AutomationRunTask,
25+
} from "./curation/automationTasks";
2326
import type { SecondsField, TaskField } from "./curation/configTypes";
2427

2528
const 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

Comments
 (0)