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.'}
+
+ )}
);
};