From 1155449e4bd4f8d4d5c35f0e1bff242eedfa1772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 00:16:28 +0000 Subject: [PATCH] a transcript with nothing in it can say nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Send a message to start" is right on the chat screen and wrong under a setup card that already says what to do — it contradicts the Start button above it and takes 200px of height doing so, which is what squeezed the new-commander pane into a scroller a third of the panel tall. `emptyState` is optional and three-valued on purpose: undefined keeps the default, a node replaces it, null shows nothing. Co-Authored-By: Claude Opus 5 --- .../apps/Chat/EmbeddableChat/EmbeddableChat.tsx | 9 +++++++++ .../Chat/EmbeddableChat/useEmbeddableChat.ts | 3 +++ .../src/apps/Chat/components/MessageList.tsx | 16 +++++++++++----- .../src/apps/CommandHQ/CommandHqDetail.tsx | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx index 250d8977..1edc2825 100644 --- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/EmbeddableChat.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import type { UseChatType } from '../../../hooks/useChat'; import { useEmbeddableChat } from './useEmbeddableChat'; import { MessageList } from '../components/MessageList'; @@ -5,6 +6,14 @@ import { InputArea } from '../components/InputArea'; type EmbeddableChatProps = { sessionId?: string; + /** + * What the transcript shows before there is anything in it. `null` shows nothing. + * + * The default, "Send a message to start", is true on the chat screen and wrong wherever something else + * already explains what to do — a new commander has a setup card and a Start button above it, so the + * message contradicts them and takes 200px of height while doing it. + */ + emptyState?: ReactNode; /** Which panel this is — see `queue-store`. Absent on the screen, where there is only one chat. */ panelId?: string; initialModel?: string | null; diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts index e95b1617..7e46354a 100644 --- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts +++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import type { KeyboardEvent } from 'react'; import { useState, useRef, useEffect, useCallback } from 'react'; import { useUserVisibleModels } from 'state/useModels'; @@ -51,6 +52,7 @@ type UseEmbeddableChatParams = { contextId?: string; /** This session is a commander, per `GET /chat/sessions/:id`. See the note where it is surfaced. */ isCommander?: boolean; + emptyState?: ReactNode; }; /** @@ -622,6 +624,7 @@ export function useEmbeddableChat(params: UseEmbeddableChatParams, onMessageComp would lose the brand-new composer, which has no session yet. */ isCommander: params.context === 'command-hq' || params.isCommander === true, + emptyState: params.emptyState, /* Claude's transcript id, which is what a commander is keyed on everywhere — the Command HQ row, the record of what it spawned, the delete cascade. `sessionId` above is OFFICER's handle for the same diff --git a/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx b/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx index edee25af..0f8284b0 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/MessageList.tsx @@ -107,11 +107,17 @@ export const MessageList = ({ manager }: MessageListProps) => { )}
- {rows.length === 0 && !isGenerating && ( -
- Send a message to start -
- )} + {rows.length === 0 && + !isGenerating && + // `undefined` means nobody said, so keep the default. `null` means "show nothing", which is what + // a screen that already explains itself above the transcript passes. + (manager.emptyState !== undefined ? ( + manager.emptyState + ) : ( +
+ Send a message to start +
+ ))} {rows.length > 0 && (
diff --git a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx index bbf7dadc..4102aa3b 100644 --- a/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx +++ b/src/workspaces/officerdev/src/apps/CommandHQ/CommandHqDetail.tsx @@ -365,6 +365,7 @@ function NewCommander({ !started && !ready ? { reason: !name.trim() ? 'Name it first…' : 'Choose a working directory first…' } : false } className={!started ? 'shrink-0' : 'min-h-0 flex-1'} + {...(!started ? { emptyState: null } : {})} /> setPickerOpen(false)} onSelect={setCwd} />