diff --git a/frontend/src/components/pilot/ProposalBanner.tsx b/frontend/src/components/pilot/ProposalBanner.tsx
new file mode 100644
index 00000000..7a760e79
--- /dev/null
+++ b/frontend/src/components/pilot/ProposalBanner.tsx
@@ -0,0 +1,118 @@
+/**
+ * ProposalBanner — chat-composer-anchored banner that carries the lifecycle
+ * of a suggested fix from Proposed → Verifying → terminal outcome.
+ *
+ * Replaces the task-lane SuggestedFix card (Phase 8). The banner renders
+ * above the chat composer in AssistantChatPage. Parent owns the fix record
+ * and the outcome mutations; this component renders + dispatches callbacks.
+ *
+ * Visual reference: docs/FlowAssist_Migration/mockups/06-slide-up-banner.html
+ * + 07-verify-states.html.
+ */
+import { Sparkles, Check, ChevronDown } from 'lucide-react'
+import type {
+ SessionSuggestedFix,
+ FixOutcome,
+} from '@/api/sessionSuggestedFixes'
+
+export type BannerMode =
+ | 'proposed' // AI just proposed; engineer hasn't applied yet
+ | 'verifying' // Engineer clicked Apply; awaiting outcome
+ | 'partial' // Applied partially; awaiting finish or terminal outcome
+ | 'ai_confirming' // AI emitted [FIX_OUTCOME]; engineer confirms
+ | 'nudge' // Compact nudge shown after N post-apply messages
+
+export interface ProposalBannerProps {
+ fix: SessionSuggestedFix
+ mode: BannerMode
+ onApply: () => void
+ onDismiss: () => void
+ onOutcome: (outcome: FixOutcome, notes?: string) => void
+ onAcceptAIProposal: () => void
+ onRejectAIProposal: () => void
+ /** Collapsed variant shown as a thin single-line strip. */
+ collapsed?: boolean
+ onToggleCollapsed?: () => void
+}
+
+export function ProposalBanner(props: ProposalBannerProps) {
+ if (props.collapsed) return
+ switch (props.mode) {
+ case 'proposed': return
+ case 'verifying': return
+ case 'partial': return
+ case 'ai_confirming': return
+ case 'nudge': return
+ }
+}
+
+function ProposedBanner({ fix, onApply, onDismiss, onToggleCollapsed }: ProposalBannerProps) {
+ return (
+
+
+
+
+
+
+
+
+ Suggested Fix
+
+ {fix.confidence_pct}% confidence
+
+
+
+ {fix.title}
+
+
+ {fix.description}
+
+ {fix.script_template_id && (
+
+
+ Matches an existing Script Library template — one-click apply
+
+ )}
+
+
+ {onToggleCollapsed && (
+
+ )}
+
+
+
+
+
+ )
+}
+
+// Placeholder renderers — implemented in Tasks 8 & 9.
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function VerifyingBanner(_: ProposalBannerProps) { return null }
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function PartialBanner(_: ProposalBannerProps) { return null }
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function AIConfirmingBanner(_: ProposalBannerProps) { return null }
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function NudgeBanner(_: ProposalBannerProps) { return null }
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function CollapsedBanner(_: ProposalBannerProps) { return null }
+
+export default ProposalBanner
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 607ee349..4f148cc0 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -86,7 +86,12 @@
--animate-slide-in-bottom: slide-in-from-bottom 200ms ease-out both;
--animate-scale-in: scale-in 150ms ease-out both;
--animate-fade: fadeIn 300ms ease both;
+ --animate-slide-up: slide-up 320ms cubic-bezier(.22,.9,.28,1) both;
+ @keyframes slide-up {
+ from { transform: translateY(14px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+ }
@keyframes fade-in {
from { opacity: 0; } to { opacity: 1; }
}