diff --git a/apps/web/src/app/(app)/claw/components/SettingsTab.tsx b/apps/web/src/app/(app)/claw/components/SettingsTab.tsx index 84ebd463f..1b612ffe6 100644 --- a/apps/web/src/app/(app)/claw/components/SettingsTab.tsx +++ b/apps/web/src/app/(app)/claw/components/SettingsTab.tsx @@ -501,6 +501,7 @@ function MorningBriefingCard({ fallbackReadiness, isRunning, actionsReady, + onRequestUpgrade, }: { mutations: ClawMutations; briefingStatus: MorningBriefingStatusLite | undefined; @@ -511,6 +512,8 @@ function MorningBriefingCard({ }; isRunning: boolean; actionsReady: boolean; + /** Callback that opens the focused upgrade confirmation flow. */ + onRequestUpgrade?: () => void; }) { const [requestedDay, setRequestedDay] = useState<'today' | 'yesterday' | null>(null); const { data: readData, isFetching: isReading } = useClawReadMorningBriefing(requestedDay, true); @@ -539,12 +542,17 @@ function MorningBriefingCard({ } as const); const hasSchedule = Boolean(briefingStatus?.cron && briefingStatus?.timezone); - const { desiredEnabled, observedEnabled, hasResolvedBriefingToggleState, isWarmupState } = - deriveMorningBriefingCardState({ - isRunning, - actionsReady, - briefingStatus, - }); + const { + desiredEnabled, + observedEnabled, + hasResolvedBriefingToggleState, + isWarmupState, + isControllerOutOfDate, + } = deriveMorningBriefingCardState({ + isRunning, + actionsReady, + briefingStatus, + }); const reconcileState = briefingStatus?.reconcileState ?? 'idle'; const lastReconcileAction = briefingStatus?.lastReconcileAction ?? null; const isTransitioning = @@ -553,6 +561,10 @@ function MorningBriefingCard({ mutations.disableMorningBriefing.isPending; const statusLabel = (() => { + if (isControllerOutOfDate) { + return 'Upgrade Required'; + } + if (isWarmupState) { return 'Instance Warming Up'; } @@ -585,13 +597,14 @@ function MorningBriefingCard({ return observedEnabled ? 'Enabled' : 'Disabled'; })(); - const statusVariant = isWarmupState - ? 'secondary' - : statusLabel === 'Instance Stopped' + const statusVariant = + isControllerOutOfDate || isWarmupState ? 'secondary' - : observedEnabled || (isTransitioning && desiredEnabled) - ? 'default' - : 'secondary'; + : statusLabel === 'Instance Stopped' + ? 'secondary' + : observedEnabled || (isTransitioning && desiredEnabled) + ? 'default' + : 'secondary'; const readySources = [ sourceReadiness.github.configured ? 'GitHub' : null, @@ -610,12 +623,17 @@ function MorningBriefingCard({ : readySources.length === 0 ? 'No sources are connected yet. Configure GitHub, Linear, or Web Search to generate richer briefings.' : `Connected sources: ${joinFriendlyList(readySources)}. Disconnected sources: ${joinFriendlyList(missingSources)}.`; - const showScheduleDetails = !isWarmupState && hasSchedule && desiredEnabled; - const controlsEnabled = actionsReady && !isWarmupState; + const showScheduleDetails = + !isWarmupState && !isControllerOutOfDate && hasSchedule && desiredEnabled; + const controlsEnabled = actionsReady && !isWarmupState && !isControllerOutOfDate; const canUseBriefingControls = controlsEnabled && desiredEnabled; const lastDelivery = briefingStatus?.lastDelivery ?? []; const showLastDelivery = - !isWarmupState && actionsReady && hasResolvedBriefingToggleState && lastDelivery.length > 0; + !isWarmupState && + !isControllerOutOfDate && + actionsReady && + hasResolvedBriefingToggleState && + lastDelivery.length > 0; const deliveryChannelLabel = { telegram: 'Telegram', discord: 'Discord', @@ -635,6 +653,28 @@ function MorningBriefingCard({ return (
Upgrade required
++ Morning Briefing requires a newer KiloClaw version. Upgrade to enable scheduling and + delivery. +
+