Skip to content

Commit c8ee688

Browse files
authored
Keep message edits expanded on mobile (#4097)
## Human comments ## What was wrong Sent-message and queued-message edits reused the follow-up composer’s compact mobile behavior. After focus left an inline edit, its draft collapsed to a single row and hid the editing controls. There is no dedicated issue for this layout bug; nearby queued-editing issues [#1864](#1864) and [#2401](#2401) are separate. ## What changed Added an edit-specific expanded preference to `FollowUpPromptBox` and opted in the main-thread sent and queued editors plus the embedded-chat queued editor. The derived expanded state drives the compact layout, mobile CSS, and height animation. The ordinary follow-up composer keeps its existing default. Synthetic before/after screenshots are in `docs/assets/message-editing/`. ## How you verified - `pnpm exec turbo run test typecheck --filter=@bb/app` — 5,077 tests passed, 6 skipped; typecheck passed. - `pnpm exec turbo run lint --filter=@bb/app` — 0 errors. - At 390 px in the source app, opened sent and queued edits and moved focus away. Before: `compact: true, expanded: false`. After: `compact: false, expanded: true`. > AGENT GENERATED
1 parent 9e6e455 commit c8ee688

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

apps/app/src/components/promptbox/FollowUpPromptBox.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,32 @@ describe("FollowUpPromptBox", () => {
917917
).toBeNull();
918918
});
919919

920+
it("shows the full editor immediately for message edits on mobile", () => {
921+
mocks.isCompactViewport = true;
922+
vi.useFakeTimers();
923+
924+
try {
925+
const props = createFollowUpPromptBoxProps({ kind: "ready" });
926+
render(<FollowUpPromptBox {...props} preferExpanded />);
927+
const promptBox = screen.getByTestId("prompt-box");
928+
const input = screen.getByRole("textbox", { name: "Follow-up prompt" });
929+
930+
expect(promptBox.getAttribute("data-compact")).toBe("false");
931+
act(() => {
932+
input.focus();
933+
input.blur();
934+
vi.advanceTimersByTime(20);
935+
});
936+
expect(promptBox.getAttribute("data-compact")).toBe("false");
937+
expect(
938+
promptBox.closest("[data-follow-up-composer-expanded]"),
939+
).not.toBeNull();
940+
expect(screen.queryByText("Ask a follow-up")).toBeNull();
941+
} finally {
942+
vi.useRealTimers();
943+
}
944+
});
945+
920946
it("collapses a wide composer until the user focuses it again", () => {
921947
const props = createFollowUpPromptBoxProps({ kind: "ready" });
922948
props.environmentSummary = <span>Local environment</span>;

apps/app/src/components/promptbox/FollowUpPromptBox.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export interface FollowUpPromptBoxProps {
167167
pluginComposerScope?: PluginComposerScope | null;
168168
textEffects?: readonly ComposerTextEffectSource[];
169169
collapseResetKey: string | number;
170+
preferExpanded?: boolean;
170171
focusEndKey?: string | number;
171172
isPrimaryComposer?: boolean;
172173
showScrollToBottomButton?: boolean;
@@ -238,6 +239,7 @@ function FollowUpPromptBoxWithComposer({
238239
pluginComposerScope,
239240
textEffects,
240241
collapseResetKey,
242+
preferExpanded = false,
241243
focusEndKey,
242244
isPrimaryComposer = true,
243245
showScrollToBottomButton = true,
@@ -296,8 +298,10 @@ function FollowUpPromptBoxWithComposer({
296298
>(null);
297299
const isWidePromptBoxCollapsed =
298300
widePromptBoxCollapsedFor === collapseResetKey;
301+
const isEditorExpanded =
302+
isInteractionExpanded || (preferExpanded && !isWidePromptBoxCollapsed);
299303
const isPromptBoxCompact =
300-
isWidePromptBoxCollapsed || (isCompactViewport && !isInteractionExpanded);
304+
isWidePromptBoxCollapsed || (isCompactViewport && !isEditorExpanded);
301305
const compactConfig = useMemo(
302306
() =>
303307
isCompactViewport || isWidePromptBoxCollapsed
@@ -685,7 +689,7 @@ function FollowUpPromptBoxWithComposer({
685689
ref={composerInteractionRef}
686690
className="relative z-20"
687691
data-follow-up-composer=""
688-
data-follow-up-composer-expanded={isInteractionExpanded ? "" : undefined}
692+
data-follow-up-composer-expanded={isEditorExpanded ? "" : undefined}
689693
hidden={hasPendingInteraction}
690694
onBlurCapture={scheduleCollapseAfterFocusLoss}
691695
onFocusCapture={handleComposerFocus}
@@ -709,7 +713,7 @@ function FollowUpPromptBoxWithComposer({
709713
focusEndKey={focusEndKey}
710714
placeholder={composer.promptPlaceholder}
711715
containerCompactPlaceholder={composer.compactPromptPlaceholder}
712-
heightAnimationKey={isInteractionExpanded ? "expanded" : "compact"}
716+
heightAnimationKey={isEditorExpanded ? "expanded" : "compact"}
713717
mentionMenuPlacement="top"
714718
submission={{
715719
label: composer.submitLabel,

apps/app/src/components/thread/embedded-chat/EmbeddedThreadChat.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ function EmbeddedThreadChatWithComposer({
10111011
typeahead={typeaheadConfig}
10121012
promptActions={promptActions}
10131013
collapseResetKey={`${surfaceKey}:queued-message:${inlineEditingQueuedMessage.queuedMessageId}`}
1014+
preferExpanded
10141015
focusEndKey={`${inlineEditingQueuedMessage.editSessionId}:${inlineComposerFocusNonce}`}
10151016
isPrimaryComposer={false}
10161017
showScrollToBottomButton={false}

apps/app/src/views/thread-detail/ThreadDetailPromptArea.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ function buildInlineDraftComposer(options: InlineDraftComposerOptions) {
287287
typeahead={options.typeahead}
288288
promptActions={options.promptActions}
289289
collapseResetKey={options.collapseResetKey}
290+
preferExpanded
290291
focusEndKey={`${options.focusSessionKey}:${options.editFocusNonce}`}
291292
isPrimaryComposer={false}
292293
showScrollToBottomButton={false}

0 commit comments

Comments
 (0)