diff --git a/frontend/src/components/assistant/ConcludeSessionModal.tsx b/frontend/src/components/assistant/ConcludeSessionModal.tsx
index 82f97c36..a1d10b34 100644
--- a/frontend/src/components/assistant/ConcludeSessionModal.tsx
+++ b/frontend/src/components/assistant/ConcludeSessionModal.tsx
@@ -33,8 +33,8 @@ interface ConcludeSessionModalProps {
const OUTCOMES: { value: ConclusionOutcome; label: string; description: string; icon: typeof CheckCircle2; color: string; bg: string; border: string }[] = [
{
value: 'resolved',
- label: 'Resolved',
- description: 'Issue has been fixed or answered',
+ label: 'Resolve',
+ description: 'Mark the issue as fixed',
icon: CheckCircle2,
color: 'text-emerald-400',
bg: 'bg-emerald-400/10',
@@ -43,7 +43,7 @@ const OUTCOMES: { value: ConclusionOutcome; label: string; description: string;
{
value: 'escalated',
label: 'Escalate',
- description: 'Needs to be handed off or escalated',
+ description: 'Hand off to another engineer or team',
icon: ArrowUpRight,
color: 'text-amber-400',
bg: 'bg-amber-400/10',
@@ -51,8 +51,8 @@ const OUTCOMES: { value: ConclusionOutcome; label: string; description: string;
},
{
value: 'paused',
- label: 'Paused',
- description: 'Continuing later — saving progress',
+ label: 'Pause',
+ description: 'Save progress and come back later',
icon: Pause,
color: 'text-blue-400',
bg: 'bg-blue-400/10',
@@ -155,7 +155,7 @@ export function ConcludeSessionModal({
setSummary('')
}
} catch {
- setError('Failed to conclude session. Please try again.')
+ setError('Failed to close case. Please try again.')
setGenerating(false)
}
}
@@ -296,7 +296,7 @@ export function ConcludeSessionModal({
{step === 'select-outcome' && (
- How did this session end?
+ What would you like to do?
{OUTCOMES.map(o => {
const Icon = o.icon
@@ -526,12 +526,12 @@ export function ConcludeSessionModal({
{generating ? (
<>
- Generating...
+ Closing...
>
) : (
<>
- Generate Summary
+ Close & Generate
>
)}
diff --git a/frontend/src/components/assistant/IncidentHeader.tsx b/frontend/src/components/assistant/IncidentHeader.tsx
index 0bf67a2c..880977a1 100644
--- a/frontend/src/components/assistant/IncidentHeader.tsx
+++ b/frontend/src/components/assistant/IncidentHeader.tsx
@@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react'
-import { Pencil, X, Check, ExternalLink, Pause, XCircle, Link2, MoreHorizontal, FileText } from 'lucide-react'
+import { Pencil, X, Check, CheckCircle2, ExternalLink, Pause, XCircle, Link2, MoreHorizontal, FileText } from 'lucide-react'
import { cn } from '@/lib/utils'
import { toast } from '@/lib/toast'
import type { TriageMeta } from '@/types/ai-session'
@@ -12,8 +12,6 @@ interface IncidentHeaderProps {
onStatusUpdate?: () => void
onPause?: () => void
onClose?: () => void
- /** Extra elements rendered in the action group (e.g. ViewToggle) */
- extraActions?: React.ReactNode
}
interface EditPopoverProps {
@@ -129,14 +127,14 @@ function OverflowMenu({ onPause, onClose }: { onPause?: () => void; onClose?: ()
{open && (
<>
setOpen(false)} />
-
+
{onPause && (
{ onPause(); setOpen(false) }}
className="w-full flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors text-left"
>
- Pause Case
+ Pause
)}
void; onClose?: ()
className="w-full flex items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-rose-400 hover:bg-rose-500/10 transition-colors text-left"
>
- Close Case
+ Close
)}
@@ -170,7 +168,6 @@ export function IncidentHeader({
onStatusUpdate,
onPause,
onClose,
- extraActions,
}: IncidentHeaderProps) {
return (
@@ -212,21 +209,21 @@ export function IncidentHeader({
)}
+
Resolve
{onStatusUpdate && (
Update
)}
- {extraActions}
)
diff --git a/frontend/src/components/assistant/ViewToggle.tsx b/frontend/src/components/assistant/ViewToggle.tsx
index fc88bfb6..1b5e0b8c 100644
--- a/frontend/src/components/assistant/ViewToggle.tsx
+++ b/frontend/src/components/assistant/ViewToggle.tsx
@@ -18,6 +18,13 @@ interface ViewToggleProps {
sessionId?: string
}
+/**
+ * Persistent tab bar for switching between FlowPilot (chat) and Cockpit (triage) views.
+ * Renders as a horizontal tab strip with an active bottom-border indicator.
+ *
+ * NOTE: If the tab bar proves too tall or prominent in certain layouts,
+ * consider pivoting to a compact segmented control (Option A from the critique).
+ */
export function ViewToggle({ currentView, sessionId }: ViewToggleProps) {
const navigate = useNavigate()
const hasCockpit = useFeatureFlag('flowpilot_cockpit')
@@ -37,19 +44,24 @@ export function ViewToggle({ currentView, sessionId }: ViewToggleProps) {
}
return (
-
+
{VIEW_OPTIONS.map(({ key, label, icon: Icon }) => (
handleSwitch(key)}
className={cn(
- 'flex items-center gap-1.5 rounded-md px-2.5 py-1 font-medium transition-colors',
+ 'flex items-center gap-1.5 px-3 py-2 text-xs font-medium border-b-2 -mb-px',
+ 'transition-[color,border-color] duration-150',
+ 'active:opacity-80',
currentView === key
- ? 'bg-elevated text-foreground'
- : 'text-muted-foreground hover:text-foreground'
+ ? 'text-foreground border-primary'
+ : 'text-muted-foreground hover:text-foreground border-transparent'
)}
>
-
+
{label}
))}
diff --git a/frontend/src/components/flowpilot/FlowPilotActionBar.tsx b/frontend/src/components/flowpilot/FlowPilotActionBar.tsx
deleted file mode 100644
index df72f812..00000000
--- a/frontend/src/components/flowpilot/FlowPilotActionBar.tsx
+++ /dev/null
@@ -1,227 +0,0 @@
-import { useState } from 'react'
-import { CheckCircle2, ArrowUpRight, Pause, X, FileText } from 'lucide-react'
-import { EscalateModal } from './EscalateModal'
-import { StatusUpdateModal } from './StatusUpdateModal'
-import type {
- ResolveSessionRequest,
- EscalateSessionRequest,
- SessionDocumentation,
- StatusUpdateAudience,
- StatusUpdateLength,
- StatusUpdateContext,
- StatusUpdateResponse,
-} from '@/types/ai-session'
-
-interface FlowPilotActionBarProps {
- canResolve: boolean
- canEscalate: boolean
- isProcessing: boolean
- hasPsaTicket?: boolean
- sessionId?: string
- canShareUpdate?: boolean
- onResolve: (data: ResolveSessionRequest) => Promise
- onEscalate: (data: EscalateSessionRequest) => Promise
- onPause?: () => Promise
- onAbandon?: () => Promise
- onGenerateStatusUpdate?: (audience: StatusUpdateAudience, length: StatusUpdateLength, context: StatusUpdateContext) => Promise
-}
-
-export function FlowPilotActionBar({
- canResolve,
- canEscalate,
- isProcessing,
- hasPsaTicket = false,
- sessionId,
- canShareUpdate = false,
- onResolve,
- onEscalate,
- onPause,
- onAbandon,
- onGenerateStatusUpdate,
-}: FlowPilotActionBarProps) {
- const [showResolve, setShowResolve] = useState(false)
- const [showEscalate, setShowEscalate] = useState(false)
- const [showAbandon, setShowAbandon] = useState(false)
- const [showStatusUpdate, setShowStatusUpdate] = useState(false)
- const [resolutionSummary, setResolutionSummary] = useState('')
- const [submitting, setSubmitting] = useState(false)
-
- const handleResolve = async () => {
- if (!resolutionSummary.trim() || resolutionSummary.length < 5) return
- setSubmitting(true)
- try {
- await onResolve({ resolution_summary: resolutionSummary })
- setShowResolve(false)
- } finally {
- setSubmitting(false)
- }
- }
-
- const handlePause = async () => {
- if (onPause) {
- setSubmitting(true)
- try {
- await onPause()
- } finally {
- setSubmitting(false)
- }
- }
- }
-
- const handleAbandon = async () => {
- if (onAbandon) {
- setSubmitting(true)
- try {
- await onAbandon()
- setShowAbandon(false)
- } finally {
- setSubmitting(false)
- }
- }
- }
-
- return (
- <>
- {/* Bottom bar — fixed to viewport bottom, single row on all screen sizes */}
-
- {/* Primary actions */}
-
{ setShowResolve(true); setShowEscalate(false) }}
- disabled={!canResolve || isProcessing}
- className="flex items-center justify-center gap-1.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-emerald-400 hover:bg-emerald-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- >
-
- Resolve
-
-
setShowEscalate(true)}
- disabled={!canEscalate || isProcessing}
- className="flex items-center justify-center gap-1.5 rounded-lg bg-amber-500/10 border border-amber-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-amber-400 hover:bg-amber-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- >
-
- Escalate
-
- {canShareUpdate && onGenerateStatusUpdate && (
-
setShowStatusUpdate(true)}
- disabled={isProcessing}
- className="flex items-center justify-center gap-1.5 rounded-lg bg-blue-500/10 border border-blue-500/20 px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-blue-400 hover:bg-blue-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- title="Share Update"
- >
-
- Share Update
-
- )}
-
- {/* Spacer */}
-
-
- {/* Secondary actions — right side */}
- {onPause && (
-
-
- Pause
-
- )}
- {onAbandon && (
-
setShowAbandon(true)}
- disabled={isProcessing || submitting}
- className="flex items-center justify-center gap-1.5 rounded-lg bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] px-2.5 sm:px-4 py-2 min-h-[40px] sm:min-h-[44px] text-xs sm:text-sm font-medium text-muted-foreground hover:text-foreground hover:border-[rgba(255,255,255,0.12)] disabled:opacity-40 disabled:pointer-events-none transition-colors"
- >
-
- Close
-
- )}
-
-
- {/* Resolve modal */}
- {showResolve && (
-
-
-
Resolve Session
-
Summarize what fixed the issue. This will be included in the auto-generated documentation.
-
-
- )}
-
- {/* Close/Abandon confirmation */}
- {showAbandon && (
-
-
-
Close Session
-
- Are you sure you want to close this session? The session history will be kept but it won't count as resolved.
-
-
- setShowAbandon(false)}
- className="rounded-lg px-4 py-2 min-h-[44px] text-sm text-muted-foreground hover:text-foreground transition-colors"
- >
- Cancel
-
-
- {submitting ? 'Closing...' : 'Close Session'}
-
-
-
-
- )}
-
- {/* Escalate modal */}
- setShowEscalate(false)}
- onEscalate={onEscalate}
- isProcessing={isProcessing || submitting}
- hasPsaTicket={hasPsaTicket}
- sessionId={sessionId}
- />
-
- {/* Status Update modal */}
- {onGenerateStatusUpdate && (
- setShowStatusUpdate(false)}
- onGenerate={onGenerateStatusUpdate}
- context="status"
- hasPsaTicket={hasPsaTicket}
- />
- )}
- >
- )
-}
diff --git a/frontend/src/components/flowpilot/index.ts b/frontend/src/components/flowpilot/index.ts
index 3fe5cc4e..b517424f 100644
--- a/frontend/src/components/flowpilot/index.ts
+++ b/frontend/src/components/flowpilot/index.ts
@@ -2,7 +2,6 @@ export { FlowPilotIntake } from './FlowPilotIntake'
export { FlowPilotSession } from './FlowPilotSession'
export { FlowPilotStepCard } from './FlowPilotStepCard'
export { FlowPilotOptions } from './FlowPilotOptions'
-export { FlowPilotActionBar } from './FlowPilotActionBar'
export { ConfidenceIndicator } from './ConfidenceIndicator'
export { SessionDocView } from './SessionDocView'
export { AISessionListItem } from './AISessionListItem'
diff --git a/frontend/src/pages/CockpitPage.tsx b/frontend/src/pages/CockpitPage.tsx
index 0171359b..9d450bd8 100644
--- a/frontend/src/pages/CockpitPage.tsx
+++ b/frontend/src/pages/CockpitPage.tsx
@@ -1,6 +1,6 @@
import { useState, useRef, useCallback, useEffect } from 'react'
import { useNavigate } from 'react-router-dom'
-import { Sparkles, Send, Loader2, Flag, MessageSquare, Paperclip, Terminal, X, RotateCcw, ImagePlus, ListChecks, FileText, GripHorizontal } from 'lucide-react'
+import { Sparkles, Send, Loader2, MessageSquare, Paperclip, Terminal, X, RotateCcw, ImagePlus, ListChecks, GripHorizontal } from 'lucide-react'
import { cn } from '@/lib/utils'
import { PageMeta } from '@/components/common/PageMeta'
import { aiSessionsApi } from '@/api/aiSessions'
@@ -254,9 +254,6 @@ export default function CockpitPage() {
Cases
- {session.activeChatId && (
-
- )}
+ {/* View tab bar — persistent when a session is active */}
+ {session.activeChatId && (
+
+ )}
+
{session.activeChatId ? (
<>
{/* Incident Header */}
@@ -275,11 +277,6 @@ export default function CockpitPage() {
onResolve={() => session.setShowConclude(true)}
onStatusUpdate={session.messages.length >= 2 ? () => session.setShowStatusUpdate(true) : undefined}
onClose={() => session.setShowConclude(true)}
- extraActions={
-
-
-
- }
/>
{/* Resizable work zone + conversation log split */}
@@ -520,18 +517,6 @@ export default function CockpitPage() {
Paste Logs
)}
- {session.messages.length >= 2 && (
- <>
-
session.setShowStatusUpdate(true)} disabled={session.loading} className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:text-blue-400 hover:bg-blue-500/10 transition-colors disabled:opacity-40" title="Share status update">
-
- Update
-
-
session.setShowConclude(true)} disabled={session.loading} className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:text-amber-400 hover:bg-amber-400/10 transition-colors disabled:opacity-40" title="Close case">
-
- Close Case
-
- >
- )}
{!session.showTaskLane && (session.activeQuestions.length > 0 || session.activeActions.length > 0) && (
{
@@ -112,12 +113,9 @@ export default function FlowPilotPage() {
className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-[var(--color-bg-elevated)] transition-colors"
>
- Chats
+ Cases
- {session.activeChatId && (
-
- )}
+ {/* View tab bar — persistent when a session is active */}
+ {session.activeChatId && (
+
+ )}
+
{session.activeChatId ? (
<>
- {/* Desktop view toggle + action bar */}
-
-
- {session.messages.length >= 2 && (
- <>
-
session.setShowStatusUpdate(true)}
- disabled={session.loading}
- className="flex items-center gap-1.5 bg-blue-500/10 border border-blue-500/20 rounded-lg px-3 py-1 text-xs font-medium text-blue-400 hover:bg-blue-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- >
-
- Update
-
-
session.setShowConclude(true)}
- disabled={session.loading}
- className="flex items-center gap-1.5 bg-amber-500/10 border border-amber-500/20 rounded-lg px-3 py-1 text-xs font-medium text-amber-400 hover:bg-amber-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- >
-
- Conclude
-
- >
- )}
+ {/* Action bar — Resolve, Update, overflow (Pause/Close) */}
+ {session.messages.length >= 2 && (
+
+
session.setShowConclude(true)}
+ disabled={session.loading}
+ className="flex items-center gap-1.5 rounded-lg bg-emerald-500/10 border border-emerald-500/20 px-3 py-1.5 text-xs font-medium text-emerald-400 hover:bg-emerald-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
+ >
+
+ Resolve
+
+
session.setShowStatusUpdate(true)}
+ disabled={session.loading}
+ className="flex items-center gap-1.5 bg-blue-500/10 border border-blue-500/20 rounded-lg px-3 py-1.5 text-xs font-medium text-blue-400 hover:bg-blue-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
+ >
+
+ Update
+
+
+
setShowOverflow(!showOverflow)}
+ className="flex items-center justify-center rounded-lg px-2 py-1.5 text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
+ >
+
+
+ {showOverflow && (
+ <>
+
setShowOverflow(false)} />
+
+
{ setShowOverflow(false); /* pause not wired on chat sessions yet */ }}
+ className="flex w-full items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
+ >
+
+ Pause
+
+
{ setShowOverflow(false); session.setShowConclude(true) }}
+ className="flex w-full items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-rose-400 hover:bg-rose-500/10 transition-colors"
+ >
+
+ Close
+
+
+ >
+ )}
+
-
-
+ )}
{/* Messages */}
@@ -297,18 +324,6 @@ export default function FlowPilotPage() {
Paste Logs
)}
- {session.messages.length >= 2 && (
- <>
- session.setShowStatusUpdate(true)} disabled={session.loading} className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:text-blue-400 hover:bg-blue-500/10 transition-colors disabled:opacity-40" title="Share status update">
-
- Update
-
- session.setShowConclude(true)} disabled={session.loading} className="flex items-center gap-1.5 rounded-lg px-2 py-1.5 text-xs text-muted-foreground hover:text-amber-400 hover:bg-amber-400/10 transition-colors disabled:opacity-40" title="Conclude session">
-
- Conclude
-
- >
- )}
{!session.showTaskLane && (session.activeQuestions.length > 0 || session.activeActions.length > 0) && (
- {/* Conclude Session Modal */}
+ {/* Close Case Modal */}
session.setShowConclude(false)}
diff --git a/frontend/src/pages/FlowPilotSessionPage.tsx b/frontend/src/pages/FlowPilotSessionPage.tsx
index 55927daf..eb783754 100644
--- a/frontend/src/pages/FlowPilotSessionPage.tsx
+++ b/frontend/src/pages/FlowPilotSessionPage.tsx
@@ -264,7 +264,7 @@ export default function FlowPilotSessionPage() {
onClick={() => setShowStatusUpdate(true)}
disabled={fp.isProcessing}
className="flex items-center gap-1.5 rounded-lg bg-blue-500/10 border border-blue-500/20 px-3 py-1.5 text-xs font-medium text-blue-400 hover:bg-blue-500/20 disabled:opacity-40 disabled:pointer-events-none transition-colors"
- title="Share Update"
+ title="Update"
>
Update
@@ -281,7 +281,7 @@ export default function FlowPilotSessionPage() {
{showOverflow && (
<>
setShowOverflow(false)} />
-
+
{ setShowOverflow(false); fp.pauseSession() }}
className="flex w-full items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-foreground hover:bg-[rgba(255,255,255,0.06)] transition-colors"
@@ -294,7 +294,7 @@ export default function FlowPilotSessionPage() {
className="flex w-full items-center gap-2 px-3 py-2 text-xs text-muted-foreground hover:text-rose-400 hover:bg-rose-500/10 transition-colors"
>
- Close Session
+ Close
>
@@ -313,7 +313,7 @@ export default function FlowPilotSessionPage() {
{showOverflow && (
<>
setShowOverflow(false)} />
-
+
{ setShowOverflow(false); setShowResolve(true) }}
disabled={!fp.canResolve}
@@ -336,7 +336,7 @@ export default function FlowPilotSessionPage() {
className="flex w-full items-center gap-2 px-3 py-2.5 text-xs text-blue-400 hover:bg-blue-500/10 transition-colors"
>
- Share Update
+ Update
)}
@@ -352,7 +352,7 @@ export default function FlowPilotSessionPage() {
className="flex w-full items-center gap-2 px-3 py-2.5 text-xs text-muted-foreground hover:text-rose-400 hover:bg-rose-500/10 transition-colors"
>
- Close Session
+ Close
>
@@ -450,7 +450,7 @@ export default function FlowPilotSessionPage() {
{showAbandon && (
-
Close Session
+
Close
Are you sure you want to close this session? The session history will be kept but it won't count as resolved.
@@ -475,7 +475,7 @@ export default function FlowPilotSessionPage() {
disabled={submitting}
className="rounded-lg bg-rose-500/20 border border-rose-500/30 px-4 py-2 min-h-[44px] text-sm font-medium text-rose-400 hover:bg-rose-500/30 disabled:opacity-50 transition-colors"
>
- {submitting ? 'Closing...' : 'Close Session'}
+ {submitting ? 'Closing...' : 'Close'}
diff --git a/frontend/src/pages/SessionHistoryPage.tsx b/frontend/src/pages/SessionHistoryPage.tsx
index 56699db7..57c096dc 100644
--- a/frontend/src/pages/SessionHistoryPage.tsx
+++ b/frontend/src/pages/SessionHistoryPage.tsx
@@ -624,7 +624,7 @@ export default function SessionHistoryPage() {
ref={closePopoverRef}
className="absolute right-0 top-full z-20 mt-2 w-72 rounded-xl border border-border bg-card p-4 shadow-xl"
>
-
Close Session
+
Close
Outcome