From dad66c9b204b966ecd68afcee2425eba63f9692e Mon Sep 17 00:00:00 2001 From: ciregenz Date: Fri, 31 Jul 2026 23:39:46 -0700 Subject: [PATCH] [eric] workflows: History shows cloud runs, their cost, and why one never started --- .../app/pages/Workflows/app/HistoryCard.tsx | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/pages/Workflows/app/HistoryCard.tsx b/frontend/src/app/pages/Workflows/app/HistoryCard.tsx index aafaafc3..5cc60bfd 100644 --- a/frontend/src/app/pages/Workflows/app/HistoryCard.tsx +++ b/frontend/src/app/pages/Workflows/app/HistoryCard.tsx @@ -3,36 +3,88 @@ import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { fetchRuns } from '@/shared/state/workflowsSlice'; import { openWorkflowMonitor } from '@/shared/state/dashboardLayoutSlice'; import { useWC, FONT_SERIF, statusChip, statusDot, statusLabel } from './uiKit'; +import type { WCPalette } from './uiKit'; import { toRunRow, whenText } from './model'; +import { useCloudRuns } from './useCloudRuns'; +import { toCloudHistoryRow } from './cloudRunRow'; +import type { CloudHistoryRow } from './cloudRunRow'; + +interface Entry extends CloudHistoryRow { + where: 'device' | 'cloud'; + open?: () => void; +} + +const Row: React.FC<{ entry: Entry; wc: WCPalette; now: Date }> = ({ entry, wc, now }) => ( +
+
+
+ {/* Why a run did not happen is a sentence, not a label, so let it wrap rather than ellipsing the part that answers the question. */} +
{entry.summary}
+
+ {[entry.where === 'cloud' ? 'Cloud' : null, whenText(entry.when, now), entry.durationText, entry.costText] + .filter(Boolean) + .join(' · ')} +
+
+ {entry.label} +
+); const HistoryCard: React.FC<{ workflowId: string; title: string }> = ({ workflowId, title }) => { const WC = useWC(); const dispatch = useAppDispatch(); const runs = useAppSelector((s) => s.workflows.runs[workflowId]); + const workflow = useAppSelector((s) => s.workflows.items[workflowId]); + const onCloud = workflow?.execution_target === 'cloud'; + const cloudRuns = useCloudRuns(workflowId, onCloud, workflow?.updated_at ?? ''); useEffect(() => { dispatch(fetchRuns(workflowId)); }, [workflowId, dispatch]); - const rows = (runs || []).slice(0, 8).map((r) => toRunRow(r, title)); + const local: Entry[] = (runs || []).map((r) => { + const row = toRunRow(r, title); + return { + id: row.id, + label: statusLabel(row.status), + tone: row.status, + summary: row.summary, + when: row.when, + durationText: row.durationText, + costText: '', + where: 'device', + open: () => dispatch(openWorkflowMonitor({ workflowId, runId: row.id })), + }; + }); + const remote: Entry[] = + cloudRuns.phase === 'answered' && cloudRuns.response.state === 'ready' + ? cloudRuns.response.runs.map((r) => ({ ...toCloudHistoryRow(r, title), where: 'cloud' as const })) + : []; + const rows = [...local, ...remote] + .sort((a, b) => (b.when?.getTime() ?? 0) - (a.when?.getTime() ?? 0)) + .slice(0, 8); + + // A history we could not load is not an empty history, and must never be drawn as one. + const cloudBlind = + onCloud && (cloudRuns.phase === 'checking' || (cloudRuns.phase === 'answered' && cloudRuns.response.state !== 'ready')); const now = new Date(); return (
History
- {rows.length === 0 &&
No runs yet.
} + {rows.length === 0 && !cloudBlind &&
No runs yet.
}
- {rows.map((r) => ( -
dispatch(openWorkflowMonitor({ workflowId, runId: r.id }))} title="Open this run" style={{ display: 'flex', alignItems: 'center', gap: 11, padding: '9px 0', borderBottom: `1px solid rgba(${WC.inkRGB},0.05)`, cursor: 'pointer' }}> -
-
-
{r.summary}
-
- {whenText(r.when, now)}{r.durationText ? ` · ${r.durationText}` : ''} -
-
- {statusLabel(r.status)} -
- ))} + {rows.map((entry) => )}
+ {cloudBlind && ( +
+ {cloudRuns.phase === 'checking' + ? 'Loading cloud runs…' + : 'Couldn’t load this workflow’s cloud runs, so any that ran are not shown here.'} +
+ )}
); };