mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-01 04:15:53 +00:00
- one gate (motionEnabled): off under reduced-motion, missing matchMedia, and vitest; every path checks it first - mount/filter-change only: count-up and bar grow-in key off the period|provider|range key, so 30s poll refreshes snap values silently instead of re-animating - first-load skeleton shimmer replaces bare scanning text (kept sr-only for screen readers); slide-in toast host for Settings/export feedback (validation errors stay inline); CSS hover-lift + press micro- interactions, all with a reduced-motion escape hatch - gsap 3.15.0 + @gsap/react 2.1.2 (+74KB raw JS; G4 flame work shares it) 244/244, typecheck + build green.
233 lines
9.1 KiB
TypeScript
233 lines
9.1 KiB
TypeScript
import { Fragment, useState } from 'react'
|
||
|
||
import { CliErrorPanel } from '../components/CliErrorPanel'
|
||
import { EmptyNote } from '../components/EmptyState'
|
||
import { Panel } from '../components/Panel'
|
||
import { SectionSkeleton } from '../components/Skeleton'
|
||
import { SegTabs } from '../components/SegTabs'
|
||
import { StaleBanner } from '../components/StaleBanner'
|
||
import { type Polled, usePolled } from '../hooks/usePolled'
|
||
import { formatCompact, formatUsd } from '../lib/format'
|
||
import { codeburn } from '../lib/ipc'
|
||
import type { DateRange, MenubarPayload, OptimizeJsonReport, Period, SessionYieldJson, WasteAction, YieldJsonReport } from '../lib/types'
|
||
|
||
type OptimizeTab = 'waste' | 'reverts' | 'abandoned' | 'fixes'
|
||
|
||
export function Optimize({ period, provider, range = null }: { period: Period; provider: string; range?: DateRange | null }) {
|
||
const overview = usePolled<MenubarPayload>(
|
||
() => range ? codeburn.getOverview(period, provider, range) : codeburn.getOverview(period, provider),
|
||
[period, provider, range?.from, range?.to],
|
||
)
|
||
return <OptimizeContent period={period} provider={provider} range={range} overview={overview} />
|
||
}
|
||
|
||
export function OptimizeContent({
|
||
period,
|
||
provider = 'all',
|
||
range = null,
|
||
overview,
|
||
refreshToken = 0,
|
||
}: {
|
||
period: Period
|
||
provider?: string
|
||
range?: DateRange | null
|
||
overview: Polled<MenubarPayload>
|
||
refreshToken?: number
|
||
}) {
|
||
const optimizeReport = usePolled<OptimizeJsonReport>(
|
||
() => range ? codeburn.getOptimizeReport(period, provider, range) : codeburn.getOptimizeReport(period, provider),
|
||
[period, provider, range?.from, range?.to, refreshToken],
|
||
)
|
||
const yieldReport = usePolled<YieldJsonReport>(
|
||
() => range ? codeburn.getYield(period, provider, range) : codeburn.getYield(period, provider),
|
||
[period, provider, range?.from, range?.to, refreshToken],
|
||
)
|
||
const [tab, setTab] = useState<OptimizeTab>('waste')
|
||
|
||
if (!overview.data) {
|
||
if (overview.error) return <CliErrorPanel error={overview.error} subject="optimize findings" />
|
||
return <SectionSkeleton label="Scanning optimize findings…" rows={5} />
|
||
}
|
||
|
||
const yieldData = yieldReport.error ? null : yieldReport.data
|
||
const revertedTotal = yieldData ? formatUsd(yieldData.summary.reverted.costUSD) : '—'
|
||
const abandonedTotal = yieldData ? formatUsd(yieldData.summary.abandoned.costUSD) : '—'
|
||
const options = [
|
||
{ value: 'waste', label: `Waste ${formatUsd(overview.data.optimize.savingsUSD)}` },
|
||
{ value: 'reverts', label: `Reverts ${revertedTotal}` },
|
||
{ value: 'abandoned', label: `Abandoned ${abandonedTotal}` },
|
||
// The Fixes tab renders topFindings (capped list), so label the count that shows.
|
||
{ value: 'fixes', label: `Fixes ${overview.data.optimize.topFindings.length.toLocaleString('en-US')}` },
|
||
]
|
||
|
||
return (
|
||
<>
|
||
{overview.error && <StaleBanner error={overview.error} />}
|
||
<SegTabs
|
||
options={options}
|
||
value={tab}
|
||
onChange={value => setTab(value as OptimizeTab)}
|
||
style={{ alignSelf: 'flex-start' }}
|
||
/>
|
||
<Panel>
|
||
{tab === 'waste' ? (
|
||
<WasteRows report={optimizeReport} />
|
||
) : tab === 'reverts' ? (
|
||
<YieldRows report={yieldReport} category="reverted" empty="No reverted sessions in this range yet." />
|
||
) : tab === 'abandoned' ? (
|
||
<YieldRows report={yieldReport} category="abandoned" empty="No abandoned sessions in this range yet." />
|
||
) : (
|
||
<FixesRows data={overview.data} />
|
||
)}
|
||
</Panel>
|
||
</>
|
||
)
|
||
}
|
||
|
||
function WasteRows({ report }: { report: Polled<OptimizeJsonReport> }) {
|
||
if (!report.data) {
|
||
if (report.error) return <CliErrorPanel error={report.error} subject="optimize findings" />
|
||
return <EmptyNote>Scanning optimize findings…</EmptyNote>
|
||
}
|
||
|
||
return (
|
||
<div className="opt-waste">
|
||
<div className="opt-summary">
|
||
{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} />
|
||
</div>
|
||
)
|
||
}
|
||
|
||
type OptimizeFinding = OptimizeJsonReport['findings'][number]
|
||
|
||
const IMPACT_ICON: Record<'high' | 'medium' | 'low', string> = {
|
||
high: '↑',
|
||
medium: '→',
|
||
low: '↓',
|
||
}
|
||
|
||
function actionText(fix: WasteAction): string {
|
||
return fix.type === 'file-content' ? fix.content : fix.text
|
||
}
|
||
|
||
function ActionableFindingRows({ findings }: { findings: OptimizeFinding[] }) {
|
||
const [expandedId, setExpandedId] = useState<string | null>(null)
|
||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||
|
||
if (!findings.length) return <EmptyNote>No waste findings in this range yet.</EmptyNote>
|
||
|
||
const copyFix = async (finding: OptimizeFinding) => {
|
||
await navigator.clipboard.writeText(actionText(finding.fix))
|
||
setCopiedId(finding.id)
|
||
window.setTimeout(() => setCopiedId(current => current === finding.id ? null : current), 1_500)
|
||
}
|
||
|
||
return (
|
||
<div className="opt-findings">
|
||
{findings.map(finding => {
|
||
const expanded = expandedId === finding.id
|
||
return (
|
||
<Fragment key={finding.id}>
|
||
<button
|
||
className="opt-finding opt-finding-toggle"
|
||
type="button"
|
||
aria-expanded={expanded}
|
||
onClick={() => setExpandedId(current => current === finding.id ? null : finding.id)}
|
||
>
|
||
<span className={`opt-impact opt-impact-${finding.severity}`}>
|
||
<span aria-hidden="true">{IMPACT_ICON[finding.severity]}</span>
|
||
{finding.severity.charAt(0).toUpperCase() + finding.severity.slice(1)}
|
||
</span>
|
||
<span className="opt-finding-titlewrap">
|
||
<b className="opt-finding-title">{finding.title}</b>
|
||
{finding.trend === 'improving' && (
|
||
<span className="opt-trend opt-trend-improving">improving<span aria-hidden="true"> ↓</span></span>
|
||
)}
|
||
</span>
|
||
<span className="opt-finding-savings">{formatUsd(finding.estimatedSavingsUSD)}</span>
|
||
<span className="opt-finding-tokens">{formatCompact(finding.tokensSaved)} tokens</span>
|
||
<span className="opt-finding-chevron" aria-hidden="true">›</span>
|
||
</button>
|
||
{expanded && (
|
||
<div className="opt-finding-detail" role="region" aria-label={`${finding.title} details`}>
|
||
<p className="opt-explanation">{finding.explanation}</p>
|
||
<div className={`opt-fix opt-fix-${finding.fix.type}`}>
|
||
<div className="opt-fix-head">
|
||
<div>
|
||
<b>{finding.fix.label}</b>
|
||
{finding.fix.type === 'file-content' && <span className="opt-fix-path">{finding.fix.path}</span>}
|
||
</div>
|
||
<button className="opt-copy" type="button" onClick={() => void copyFix(finding)}>
|
||
{copiedId === finding.id ? 'Copied' : 'Copy'}
|
||
</button>
|
||
</div>
|
||
<pre className="opt-fix-code"><code>{actionText(finding.fix)}</code></pre>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Fragment>
|
||
)
|
||
})}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
type Finding = MenubarPayload['optimize']['topFindings'][number]
|
||
|
||
function FindingRows({ findings, empty }: { findings: Finding[]; empty: string }) {
|
||
if (!findings.length) return <EmptyNote>{empty}</EmptyNote>
|
||
|
||
return (
|
||
<div className="opt-findings">
|
||
{findings.map((finding, i) => (
|
||
<div className="opt-finding opt-finding-legacy" key={`${finding.title}-${i}`}>
|
||
<span className="opt-finding-rank">{String(i + 1).padStart(2, '0')}</span>
|
||
<b className="opt-finding-title">{finding.title}</b>
|
||
<span className={`opt-impact opt-impact-${finding.impact}`}>
|
||
<span aria-hidden="true">{IMPACT_ICON[finding.impact]}</span>
|
||
{finding.impact.charAt(0).toUpperCase() + finding.impact.slice(1)}
|
||
</span>
|
||
<span className="opt-finding-savings">{formatUsd(finding.savingsUSD)}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function YieldRows({
|
||
report,
|
||
category,
|
||
empty,
|
||
}: {
|
||
report: Polled<YieldJsonReport>
|
||
category: SessionYieldJson['category']
|
||
empty: string
|
||
}) {
|
||
if (report.error || !report.data) return <EmptyNote>Yield data is unavailable right now.</EmptyNote>
|
||
|
||
const rows = report.data.details.filter(row => row.category === category)
|
||
if (!rows.length) return <EmptyNote>{empty}</EmptyNote>
|
||
|
||
return (
|
||
<>
|
||
{rows.map((row, i) => (
|
||
<div className="li" style={{ alignItems: 'flex-start' }} key={row.sessionId}>
|
||
<span className="no">{String(i + 1).padStart(2, '0')}</span>
|
||
<div className="lx">
|
||
<b>{row.project}</b>
|
||
<span>
|
||
{row.commitCount.toLocaleString('en-US')} {row.commitCount === 1 ? 'commit' : 'commits'} · {row.sessionId}
|
||
</span>
|
||
</div>
|
||
<span className="val">{formatUsd(row.costUSD)}</span>
|
||
</div>
|
||
))}
|
||
</>
|
||
)
|
||
}
|
||
|
||
function FixesRows({ data }: { data: MenubarPayload }) {
|
||
return <FindingRows findings={data.optimize.topFindings} empty="No fixes in this range yet." />
|
||
}
|