mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-20 14:04:25 +00:00
optimize: show the applied-fix verdicts in the TUI and desktop app
Same section, compact: one line per still-applied fix with the verdict glyph, and the undo command for the ones that measured nothing. The app reads appliedFixes[] off the optimize JSON, tolerating its absence from an older CLI.
This commit is contained in:
parent
52eb9fdb8d
commit
3936284811
5 changed files with 119 additions and 2 deletions
|
|
@ -437,6 +437,17 @@ export type OptimizeJsonReport = {
|
|||
basis: 'measured' | 'estimated'
|
||||
fix: WasteAction
|
||||
}>
|
||||
/** Still-applied fixes, re-measured on every run. Absent on older CLIs. */
|
||||
appliedFixes?: Array<{
|
||||
id: string
|
||||
kind: string
|
||||
findingId: string | null
|
||||
appliedAt: string
|
||||
verdict: 'worked' | 'partial' | 'no-effect' | 'pending'
|
||||
estimatedTokens: number
|
||||
realizedTokens: number
|
||||
undoCommand: string
|
||||
}>
|
||||
}
|
||||
|
||||
// ————— T1b: src/sharing/* (defined by the shared contract) —————
|
||||
|
|
|
|||
|
|
@ -129,6 +129,34 @@ describe('Optimize', () => {
|
|||
Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText } })
|
||||
})
|
||||
|
||||
it('lists applied fixes with a glyph per verdict and the undo hint', async () => {
|
||||
const report = makeOptimizeReport()
|
||||
report.appliedFixes = [
|
||||
{ id: 'a1', kind: 'archive-skill', findingId: 'unused-skills', appliedAt: '2026-07-06T00:00:00.000Z', verdict: 'worked', estimatedTokens: 300_000, realizedTokens: 280_000, undoCommand: 'codeburn act undo a1' },
|
||||
{ id: 'b2', kind: 'defer-threshold', findingId: 'mcp-defer-threshold', appliedAt: '2026-07-07T00:00:00.000Z', verdict: 'partial', estimatedTokens: 600_000, realizedTokens: 420_000, undoCommand: 'codeburn act undo b2' },
|
||||
{ id: 'c3', kind: 'shell-config', findingId: 'bash-output-cap', appliedAt: '2026-07-05T00:00:00.000Z', verdict: 'no-effect', estimatedTokens: 41_000, realizedTokens: 0, undoCommand: 'codeburn act undo c3' },
|
||||
{ id: 'd4', kind: 'mcp-remove', findingId: null, appliedAt: '2026-07-09T00:00:00.000Z', verdict: 'pending', estimatedTokens: 0, realizedTokens: 0, undoCommand: 'codeburn act undo d4' },
|
||||
]
|
||||
getOptimizeReport.mockResolvedValue(report)
|
||||
render(<Optimize period="30days" provider="all" />)
|
||||
|
||||
await screen.findByText('Applied fixes')
|
||||
const rows = [...document.querySelectorAll('.opt-applied-row')]
|
||||
expect(rows.map(r => r.className.split(' ')[1])).toEqual([
|
||||
'opt-applied-worked', 'opt-applied-partial', 'opt-applied-no-effect', 'opt-applied-pending',
|
||||
])
|
||||
expect(rows[0]!.textContent).toContain('unused-skills')
|
||||
expect(rows[0]!.textContent).toContain('est. 300K \u2192 280K')
|
||||
expect(rows[3]!.textContent).toContain('mcp-remove')
|
||||
expect(screen.getByText('codeburn act undo c3')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('omits the applied-fixes list when nothing is applied', async () => {
|
||||
render(<Optimize period="30days" provider="all" />)
|
||||
await screen.findByText('Opus is doing your small talk')
|
||||
expect(document.querySelector('.opt-applied')).toBeNull()
|
||||
})
|
||||
|
||||
it('groups Waste findings under the fix / habits / FYI headers in order', async () => {
|
||||
render(<Optimize period="30days" provider="all" />)
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,50 @@ function WasteRows({ report }: { report: Polled<OptimizeJsonReport> }) {
|
|||
{report.data.summary.findingCount.toLocaleString('en-US')} findings · {formatUsd(report.data.summary.potentialSavingsCostUSD)} potential · health {report.data.summary.healthScore}/100
|
||||
</div>
|
||||
<ActionableFindingRows findings={report.data.findings} byClass={report.data.summary.byClass} />
|
||||
<AppliedFixRows fixes={report.data.appliedFixes ?? []} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type AppliedFix = NonNullable<OptimizeJsonReport['appliedFixes']>[number]
|
||||
|
||||
const VERDICT_GLYPH: Record<AppliedFix['verdict'], string> = {
|
||||
worked: '\u2713',
|
||||
partial: '~',
|
||||
'no-effect': '\u2717',
|
||||
pending: '\u2026',
|
||||
}
|
||||
|
||||
const VERDICT_LABEL: Record<AppliedFix['verdict'], string> = {
|
||||
worked: 'worked',
|
||||
partial: 'under estimate',
|
||||
'no-effect': 'did not help',
|
||||
pending: 'measuring',
|
||||
}
|
||||
|
||||
// Closes the loop after `optimize --apply`: what each applied fix actually
|
||||
// measured, and for the ones that did nothing, how to put them back.
|
||||
function AppliedFixRows({ fixes }: { fixes: AppliedFix[] }) {
|
||||
if (!fixes.length) return null
|
||||
|
||||
return (
|
||||
<div className="opt-findings opt-applied">
|
||||
<div className="opt-group">Applied fixes</div>
|
||||
{fixes.map(fix => (
|
||||
<div className={`opt-applied-row opt-applied-${fix.verdict}`} key={fix.id}>
|
||||
<span className="opt-applied-glyph" aria-hidden="true">{VERDICT_GLYPH[fix.verdict]}</span>
|
||||
<b className="opt-finding-title">{fix.findingId ?? fix.kind}</b>
|
||||
<span className="opt-applied-verdict">{VERDICT_LABEL[fix.verdict]}</span>
|
||||
<span className="opt-finding-tokens">
|
||||
{fix.verdict === 'pending'
|
||||
? '\u2014'
|
||||
: `est. ${formatCompact(fix.estimatedTokens)} \u2192 ${formatCompact(fix.realizedTokens)}`}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{fixes.some(fix => fix.verdict === 'no-effect') && (
|
||||
<div className="opt-summary opt-applied-hint">Revert one that did not help: <code>{fixes.find(fix => fix.verdict === 'no-effect')!.undoCommand}</code></div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,6 +671,15 @@ td:first-child { font-size: var(--fs-body); font-weight: var(--fw-body); }
|
|||
.opt-fix-code { max-width: 100%; overflow-x: auto; margin: 0; padding: 10px 11px; border: 1px solid var(--line); border-radius: 6px; background: var(--phead); color: var(--ink); font-family: var(--mono); font-size: 11px; line-height: 1.5; white-space: pre; }
|
||||
.opt-fix-command .opt-fix-code code::before { content: '$ '; color: var(--mut2); user-select: none; }
|
||||
.opt-copy { flex: 0 0 auto; padding: 4px 9px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); color: var(--mut); font: inherit; font-size: 10.5px; cursor: pointer; }
|
||||
.opt-applied { padding-top: 12px; }
|
||||
.opt-applied-row { display: grid; grid-template-columns: 16px minmax(0, 1fr) 110px 140px; align-items: center; column-gap: 12px; min-height: 34px; border-top: 1px solid var(--line2); }
|
||||
.opt-applied-glyph { color: var(--mut2); font-family: var(--mono); font-size: 12px; }
|
||||
.opt-applied-verdict { color: var(--mut); font-size: 10.5px; }
|
||||
.opt-applied-worked .opt-applied-glyph, .opt-applied-worked .opt-applied-verdict { color: var(--ok); }
|
||||
.opt-applied-partial .opt-applied-glyph, .opt-applied-partial .opt-applied-verdict { color: var(--warn); }
|
||||
.opt-applied-no-effect .opt-applied-glyph, .opt-applied-no-effect .opt-applied-verdict { color: var(--bad); }
|
||||
.opt-applied-hint { padding: 9px 0 0; }
|
||||
.opt-applied-hint code { font-family: var(--mono); }
|
||||
.opt-copy:hover, .opt-copy:focus-visible { border-color: var(--accent); color: var(--ink); outline: none; }
|
||||
.ov-analytics-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: stretch; }
|
||||
.ov-analytics-row > :only-child { grid-column: 1 / -1; }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { aggregateModelTotals } from './model-breakdown.js'
|
|||
import { buildDurablePeriod } from './usage-aggregator.js'
|
||||
import { getAllProviders } from './providers/index.js'
|
||||
import { classHeaderLine, classTotals, findingBasis, findingClass, scanAndDetect, type FindingClass, type WasteFinding, type WasteAction, type OptimizeResult } from './optimize.js'
|
||||
import { appliedFixGlyph, formatAppliedFix, type AppliedFix } from './act/types.js'
|
||||
import { aggregateFileChurn, buildCoachingNotes, computePricingCoverage, medianTimeToFirstEditMs, scanUserCorrections, worstOneShotCategory, type ReworkedFile } from './workflow-insights.js'
|
||||
import { estimateContextBudget, type ContextBudget } from './context-budget.js'
|
||||
import { dateKey } from './day-aggregator.js'
|
||||
|
|
@ -1094,7 +1095,14 @@ const GRADE_COLORS: Record<string, string> = { A: '#5BF5A0', B: '#5BF5A0', C: GO
|
|||
// off the alt-buffer top and the user couldn't see the StatusBar at all.
|
||||
const FINDINGS_WINDOW_SIZE = 3
|
||||
|
||||
function OptimizeView({ findings, costRate, projects, label, width, healthScore, healthGrade, cursor }: { findings: WasteFinding[]; costRate: number; projects: ProjectSummary[]; label: string; width: number; healthScore: number; healthGrade: string; cursor: number }) {
|
||||
const APPLIED_FIX_COLORS: Record<AppliedFix['verdict'], string> = {
|
||||
worked: '#5BF5A0',
|
||||
partial: GOLD,
|
||||
'no-effect': '#F55B5B',
|
||||
pending: DIM,
|
||||
}
|
||||
|
||||
function OptimizeView({ findings, costRate, projects, label, width, healthScore, healthGrade, cursor, appliedFixes = [] }: { findings: WasteFinding[]; costRate: number; projects: ProjectSummary[]; label: string; width: number; healthScore: number; healthGrade: string; cursor: number; appliedFixes?: AppliedFix[] }) {
|
||||
const periodCost = projects.reduce((s, p) => s + p.totalCostUSD, 0)
|
||||
const totalTokens = findings.reduce((s, f) => s + f.tokensSaved, 0)
|
||||
const totalCost = totalTokens * costRate
|
||||
|
|
@ -1132,6 +1140,16 @@ function OptimizeView({ findings, costRate, projects, label, width, healthScore,
|
|||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{appliedFixes.length > 0 && (
|
||||
<Box flexDirection="column" paddingX={1} width={width}>
|
||||
<Text bold color={ORANGE} wrap="truncate-end">Applied fixes</Text>
|
||||
{appliedFixes.map(fix => (
|
||||
<Text key={fix.id} color={APPLIED_FIX_COLORS[fix.verdict]} wrap="truncate-end">
|
||||
{appliedFixGlyph(fix)} {formatAppliedFix(fix)}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1313,6 +1331,7 @@ export function InteractiveDashboard({ initialProjects, initialDailyHistoryProje
|
|||
const [detectedProviders, setDetectedProviders] = useState<string[]>([])
|
||||
const [view, setView] = useState<View>('dashboard')
|
||||
const [optimizeResult, setOptimizeResult] = useState<OptimizeResult | null>(null)
|
||||
const [appliedFixes, setAppliedFixes] = useState<AppliedFix[]>([])
|
||||
const [optimizeLoading, setOptimizeLoading] = useState(false)
|
||||
const [projectBudgets, setProjectBudgets] = useState<Map<string, ContextBudget>>(new Map())
|
||||
const [planUsages, setPlanUsages] = useState<PlanUsage[]>(initialPlanUsages ?? [])
|
||||
|
|
@ -1473,6 +1492,12 @@ export function InteractiveDashboard({ initialProjects, initialDailyHistoryProje
|
|||
try {
|
||||
const result = await scanAndDetect(projects, currentRange(), activeProvider)
|
||||
if (reloadGenerationRef.current === generation) setOptimizeResult(result)
|
||||
// Best effort: a bad journal never keeps the findings off screen.
|
||||
try {
|
||||
const { computeActReport } = await import('./act/report.js')
|
||||
const applied = await computeActReport()
|
||||
if (reloadGenerationRef.current === generation) setAppliedFixes(applied.appliedFixes)
|
||||
} catch { /* the applied section is optional */ }
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
|
|
@ -1637,7 +1662,7 @@ export function InteractiveDashboard({ initialProjects, initialDailyHistoryProje
|
|||
{view === 'compare'
|
||||
? <CompareView projects={projects} onBack={() => setView('dashboard')} />
|
||||
: view === 'optimize' && optimizeResult
|
||||
? <OptimizeView findings={optimizeResult.findings} costRate={optimizeResult.costRate} projects={projects} label={headerLabel} width={dashWidth} healthScore={optimizeResult.healthScore} healthGrade={optimizeResult.healthGrade} cursor={findingsCursor} />
|
||||
? <OptimizeView findings={optimizeResult.findings} costRate={optimizeResult.costRate} projects={projects} label={headerLabel} width={dashWidth} healthScore={optimizeResult.healthScore} healthGrade={optimizeResult.healthGrade} cursor={findingsCursor} appliedFixes={appliedFixes} />
|
||||
: <DashboardContent projects={projects} period={period} columns={columns} maxContentWidth={maxContentWidth} activeProvider={activeProvider} budgets={projectBudgets} planUsages={planUsages} label={headerLabel} dayMode={isDayMode} dailyHistoryProjects={dailyHistoryProjects} dailyHistoryPageSize={dailyHistoryPageSize} scrollableDailyHistory={scrollableDailyHistory} dailyHistoryCursor={Math.min(dailyHistoryCursor, dailyHistoryMaxCursor)} durable={durable} />}
|
||||
{coachingNote && (
|
||||
<Box width={dashWidth} paddingX={1}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue