/** Restrained, accessible HUX-09 suggestion card. No action runs implicitly. */ import {useEffect, useState} from "react"; import {buildActionIntent, onboardingEnabled} from "./model.ts"; import type { OnboardingActionIntent, OnboardingClient, SuggestionDecision, SuggestionEvaluation, } from "./types.ts"; interface OnboardingSuggestionsProps { flags?: Iterable; client: OnboardingClient; evaluation: SuggestionEvaluation | null; disabled?: boolean; onAction(intent: OnboardingActionIntent): void | Promise; onResolved?(decision: SuggestionDecision): void; } const CONTEXT_LABELS = { first_session: "Shown because this is a new workspace session.", empty_project: "Shown because this project has no conversations yet.", after_artifact: "Shown because you just created an artifact.", after_research: "Shown because a research task just finished.", after_approval: "Shown because an approved task just finished.", idle: "Shown after this conversation became idle.", } as const; function actionLabel(evaluation: SuggestionEvaluation): string | null { const {action} = evaluation.suggestion; if (action.type === "open_mode") return "Choose a mode"; if (action.type === "create_project") return "Add to a project"; if (action.type === "open_memory") return "Review memory"; if (action.type === "open_artifacts") return "View artifacts"; if (action.type === "start_workflow") { return action.payload?.intent === "schedule_weekly" ? "Run weekly" : "Save workflow"; } return null; } /** Render nothing until the four HUX dependencies and an eligible claim exist. */ export function OnboardingSuggestions(props: OnboardingSuggestionsProps) { const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [resolved, setResolved] = useState(false); const enabled = onboardingEnabled(props.flags) && props.client?.enabled(); const evaluation = props.evaluation; useEffect(() => { setBusy(false); setError(""); setResolved(false); }, [evaluation?.claimId]); if (!enabled || !evaluation || evaluation.privacy.noStore || resolved) return null; if (evaluation.suggestion.action.type === "open_memory" && !evaluation.privacy.memorySuggestionsAllowed) return null; const label = actionLabel(evaluation); async function resolve(decision: SuggestionDecision): Promise { if (busy || props.disabled) return; setBusy(true); setError(""); try { if (decision === "acted") { const intent = buildActionIntent(evaluation); if (!intent) throw new Error("This suggestion has no safe action"); await props.onAction(intent); } await props.client.decide(evaluation, decision); setResolved(true); props.onResolved?.(decision); } catch (_) { setError("That choice could not be saved. Nothing else was changed."); } finally { setBusy(false); } } return ( ); }