refactor: improve plan mode UX with composer-integrated toggle and guidance text

- Move plan mode toggle from pills row to composer send button area
- Plan toggle sits next to the send button as a mode switch icon
- Send button turns emerald when plan mode is active
- Add mode indicator strip below composer when plan mode is on
- Replace 'Implement this plan' auto-send button with instructional
  guidance text that tells the user to send a follow-up message
- Keep Dismiss button to clear the plan review banner

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-26 23:46:04 +01:00
co-authored by Copilot
parent 231be36e6c
commit b670680a7d
2 changed files with 63 additions and 73 deletions
+55 -47
View File
@@ -130,12 +130,6 @@ export function ChatPane({
void onSend(content); void onSend(content);
} }
function handleImplementPlan() {
if (!pendingPlanReview) return;
onDismissPlanReview?.();
void onSend('Implement the plan.');
}
function handleDismissPlan() { function handleDismissPlan() {
onDismissPlanReview?.(); onDismissPlanReview?.();
} }
@@ -399,7 +393,6 @@ export function ChatPane({
<div className="mb-3"> <div className="mb-3">
<PlanReviewBanner <PlanReviewBanner
onDismiss={handleDismissPlan} onDismiss={handleDismissPlan}
onImplement={handleImplementPlan}
planReview={pendingPlanReview} planReview={pendingPlanReview}
/> />
</div> </div>
@@ -426,22 +419,6 @@ export function ChatPane({
onUpdate={onUpdateSessionApprovalSettings} onUpdate={onUpdateSessionApprovalSettings}
/> />
)} )}
{onSetInteractionMode && (
<button
aria-pressed={isPlanMode}
className={`inline-flex items-center gap-1 rounded-lg border px-2.5 py-1 text-[11px] font-medium transition ${
isPlanMode
? 'border-emerald-500/40 bg-emerald-500/15 text-emerald-300 hover:bg-emerald-500/25'
: 'border-zinc-700 text-zinc-400 hover:border-zinc-600 hover:text-zinc-300'
}`}
disabled={isComposerDisabled}
onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
type="button"
>
<ClipboardList className="size-3" />
Plan
</button>
)}
{primaryAgent && ( {primaryAgent && (
<div className="ml-auto flex items-center gap-2"> <div className="ml-auto flex items-center gap-2">
<InlineModelPill <InlineModelPill
@@ -521,32 +498,63 @@ export function ChatPane({
: 'Message...' : 'Message...'
} }
> >
<button <div className="absolute bottom-2 right-2 flex items-center gap-1">
className={`absolute bottom-2 right-2 flex size-8 items-center justify-center rounded-lg transition ${ {/* Plan mode toggle */}
isSessionBusy {onSetInteractionMode && !isSessionBusy && (
? 'bg-red-600/80 text-white hover:bg-red-500' <button
: canSubmitInput aria-label={isPlanMode ? 'Switch to interactive mode' : 'Switch to plan mode'}
? 'bg-indigo-600 text-white hover:bg-indigo-500' aria-pressed={isPlanMode}
: 'bg-zinc-800 text-zinc-600' className={`flex size-8 items-center justify-center rounded-lg transition ${
}`} isPlanMode
disabled={!canSubmitInput && !isSessionBusy} ? 'bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30'
onClick={() => { : 'text-zinc-500 hover:bg-zinc-800 hover:text-zinc-300'
if (isSessionBusy) { }`}
onCancelTurn?.(); disabled={isComposerDisabled}
} else { onClick={() => onSetInteractionMode(isPlanMode ? 'interactive' : 'plan')}
composerRef.current?.submit(); type="button"
} >
}} <ClipboardList className="size-3.5" />
type="button" </button>
aria-label={isSessionBusy ? 'Stop generating' : 'Send message'}
>
{isSessionBusy ? (
<Square className="size-3.5" fill="currentColor" />
) : (
<ArrowUp className="size-4" />
)} )}
</button>
{/* Send / Stop button */}
<button
className={`flex size-8 items-center justify-center rounded-lg transition ${
isSessionBusy
? 'bg-red-600/80 text-white hover:bg-red-500'
: canSubmitInput
? isPlanMode
? 'bg-emerald-600 text-white hover:bg-emerald-500'
: 'bg-indigo-600 text-white hover:bg-indigo-500'
: 'bg-zinc-800 text-zinc-600'
}`}
disabled={!canSubmitInput && !isSessionBusy}
onClick={() => {
if (isSessionBusy) {
onCancelTurn?.();
} else {
composerRef.current?.submit();
}
}}
type="button"
aria-label={isSessionBusy ? 'Stop generating' : isPlanMode ? 'Send as plan request' : 'Send message'}
>
{isSessionBusy ? (
<Square className="size-3.5" fill="currentColor" />
) : (
<ArrowUp className="size-4" />
)}
</button>
</div>
</MarkdownComposer> </MarkdownComposer>
{isPlanMode && !isSessionBusy && (
<div className="flex items-center gap-1.5 px-3 pb-1.5 pt-0.5">
<div className="size-1.5 rounded-full bg-emerald-500" />
<span className="text-[10px] font-medium text-emerald-400/80">
Plan mode the agent will propose a plan instead of implementing
</span>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -1,22 +1,16 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { ClipboardList, Play, X } from 'lucide-react'; import { ClipboardList, X } from 'lucide-react';
import { MarkdownContent } from '@renderer/components/MarkdownContent'; import { MarkdownContent } from '@renderer/components/MarkdownContent';
import type { PendingPlanReviewRecord } from '@shared/domain/planReview'; import type { PendingPlanReviewRecord } from '@shared/domain/planReview';
export function PlanReviewBanner({ export function PlanReviewBanner({
planReview, planReview,
onImplement,
onDismiss, onDismiss,
}: { }: {
planReview: PendingPlanReviewRecord; planReview: PendingPlanReviewRecord;
onImplement: (planReview: PendingPlanReviewRecord) => void;
onDismiss: (planReview: PendingPlanReviewRecord) => void; onDismiss: (planReview: PendingPlanReviewRecord) => void;
}) { }) {
const handleImplement = useCallback(() => {
onImplement(planReview);
}, [planReview, onImplement]);
const handleDismiss = useCallback(() => { const handleDismiss = useCallback(() => {
onDismiss(planReview); onDismiss(planReview);
}, [planReview, onDismiss]); }, [planReview, onDismiss]);
@@ -63,26 +57,14 @@ export function PlanReviewBanner({
<MarkdownContent content={planReview.planContent} /> <MarkdownContent content={planReview.planContent} />
</div> </div>
)} )}
</div>
</div>
{/* Actions */} {/* Guidance */}
<div className="mt-3 flex items-center gap-2"> <p className="mt-3 text-[12px] leading-relaxed text-zinc-400">
<button Send a follow-up message to proceed e.g.{' '}
className="inline-flex items-center gap-1.5 rounded-lg bg-emerald-600 px-3.5 py-1.5 text-[12px] font-medium text-white transition hover:bg-emerald-500" <span className="text-zinc-300">&quot;implement the plan&quot;</span>,{' '}
onClick={handleImplement} <span className="text-zinc-300">&quot;adjust step 3&quot;</span>, or ask for a different approach.
type="button" </p>
> </div>
<Play className="size-3" />
Implement this plan
</button>
<button
className="rounded-lg border border-zinc-600 px-3.5 py-1.5 text-[12px] font-medium text-zinc-300 transition hover:border-zinc-500 hover:bg-zinc-800 hover:text-white"
onClick={handleDismiss}
type="button"
>
Dismiss
</button>
</div> </div>
</div> </div>
); );