fix(chat): reading-width container + refresh placeholder on agent switch

- Wrap ChatMessageList and ChatInput in mx-auto max-w-4xl px-5 so wide
  chat windows don't sprawl — matches the issue-detail / project-detail
  width convention
- draftKey now includes the selected agent id in the new-chat state.
  Tiptap's Placeholder only applies at mount, so key-driven remount is
  the simplest way to refresh it when the user switches agents before
  sending the first message. Side benefit: per-agent new-chat drafts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Naiyuan Qing
2026-04-14 18:42:55 +08:00
parent d779cbd183
commit 34e452776b
2 changed files with 29 additions and 19 deletions

View File

@@ -30,7 +30,15 @@ export function ChatInput({
}: ChatInputProps) {
const editorRef = useRef<ContentEditorRef>(null);
const activeSessionId = useChatStore((s) => s.activeSessionId);
const draftKey = activeSessionId ?? DRAFT_NEW_SESSION;
const selectedAgentId = useChatStore((s) => s.selectedAgentId);
// Scope the new-chat draft by agent:
// 1. Switching agents while composing a brand-new chat gives each
// agent its own draft (no cross-agent leakage).
// 2. Tiptap's Placeholder extension is only applied at mount; this
// key changes on agent switch so the editor remounts and the
// `Tell {agent} what to do…` placeholder refreshes.
const draftKey =
activeSessionId ?? `${DRAFT_NEW_SESSION}:${selectedAgentId ?? ""}`;
// Select a primitive — empty-string fallback keeps referential stability.
const inputDraft = useChatStore((s) => s.inputDrafts[draftKey] ?? "");
const setInputDraft = useChatStore((s) => s.setInputDraft);
@@ -65,8 +73,8 @@ export function ChatInput({
: "Tell me what to do…";
return (
<div className="p-2 pt-0">
<div className="relative flex min-h-16 max-h-40 flex-col rounded-lg bg-card pb-9 border-1 border-border transition-colors focus-within:border-brand">
<div className="px-5 pb-3 pt-0">
<div className="relative mx-auto flex min-h-16 max-h-40 w-full max-w-4xl flex-col rounded-lg bg-card pb-9 border-1 border-border transition-colors focus-within:border-brand">
<div className="flex-1 min-h-0 overflow-y-auto px-3 py-2">
<ContentEditor
// Remount the editor when the active session changes so its

View File

@@ -53,22 +53,24 @@ export function ChatMessageList({
const hasLive = showLiveTimeline && liveTimeline.length > 0;
return (
<div
ref={scrollRef}
style={fadeStyle}
className="flex-1 overflow-y-auto px-4 py-3 space-y-4"
>
{messages.map((msg) => (
<MessageBubble key={msg.id} message={msg} />
))}
{hasLive && (
<div className="w-full space-y-1.5">
<TimelineView items={liveTimeline} />
</div>
)}
{isWaiting && !hasLive && !pendingAlreadyPersisted && (
<Loader2 className="size-4 animate-spin text-muted-foreground" />
)}
<div ref={scrollRef} style={fadeStyle} className="flex-1 overflow-y-auto">
{/* Inner container matches issue / project detail width convention
* (max-w-4xl + mx-auto) so switching between chat and content
* views doesn't jolt the reading width. px-5 is a touch tighter
* than issue-detail's px-8 because the chat window can be narrow. */}
<div className="mx-auto w-full max-w-4xl px-5 py-4 space-y-4">
{messages.map((msg) => (
<MessageBubble key={msg.id} message={msg} />
))}
{hasLive && (
<div className="w-full space-y-1.5">
<TimelineView items={liveTimeline} />
</div>
)}
{isWaiting && !hasLive && !pendingAlreadyPersisted && (
<Loader2 className="size-4 animate-spin text-muted-foreground" />
)}
</div>
</div>
);
}