Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import {
type ExistingThreadSettingsRouteSession,
useExistingThreadSettingsRoutePresentation,
} from "./ThreadSettingsSheet";
import { composerSettingsToolbarLayout } from "./composer-draft-expansion";
import {
useThreadSettingsSheetPresentation,
type NavigationWithFinishTransitioning,
Expand Down Expand Up @@ -415,6 +416,10 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
const isExpanded = isFocused || settingsSheetPresentation.keepsComposerExpanded;
const showsCompactDictation = isVoiceInputPresented && !isExpanded;
const isToolbarVisible = isExpanded || isVoiceInputPresented;
const settingsToolbar = composerSettingsToolbarLayout({
isExpanded,
isVoicePresented: isVoiceInputPresented,
});
const attachmentBlockReason = composerAttachmentUploadBlockReason({
environmentId: props.environmentId,
attachments: props.draftAttachments,
Expand Down Expand Up @@ -726,7 +731,6 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
) : null}
<Animated.View
className={isExpanded ? "px-[14px]" : "min-w-0 flex-1 px-[4px]"}
layout={COMPOSER_LAYOUT_TRANSITION}
>
<ComposerEditor
draftKey={composerOwnerKey}
Expand Down Expand Up @@ -900,21 +904,16 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
) : null}
{isExpanded ? <View className="h-1" /> : null}
</ComposerDictationDraftContent>
{settingsToolbar.mount ? (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium threads/ThreadComposer.tsx:907

When dictation is active while the composer is collapsed, the hidden editor still occupies its 36px layout height, so mounting the toolbar in normal flow produces an approximately 80px surface with a blank, non-interactive gap instead of the compact dictation strip. Keep the toolbar absolutely overlaid for this state, as the previous !isExpanded layout did.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/ThreadComposer.tsx around line 907:

When dictation is active while the composer is collapsed, the hidden editor still occupies its 36px layout height, so mounting the toolbar in normal flow produces an approximately 80px surface with a blank, non-interactive gap instead of the compact dictation strip. Keep the toolbar absolutely overlaid for this state, as the previous `!isExpanded` layout did.

<Animated.View
accessibilityElementsHidden={!isToolbarVisible}
collapsable={false}
importantForAccessibility={isToolbarVisible ? "auto" : "no-hide-descendants"}
layout={COMPOSER_LAYOUT_TRANSITION}
pointerEvents={isToolbarVisible ? "auto" : "none"}
style={
isExpanded
? undefined
: {
position: "absolute",
bottom: 2,
left: 0,
right: 0,
}
settingsToolbar.overlayEditor
? { position: "absolute", bottom: 2, left: 0, right: 0 }
: undefined
}
>
<ComposerDictationToolbar
Expand Down Expand Up @@ -991,6 +990,7 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
</ComposerToolbarRow>
</ComposerDictationToolbar>
</Animated.View>
) : null}
</ComposerSurface>
</Animated.View>

Expand Down
29 changes: 29 additions & 0 deletions apps/mobile/src/features/threads/composer-draft-expansion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vite-plus/test";

import { composerSettingsToolbarLayout } from "./composer-draft-expansion";

describe("composer settings toolbar layout", () => {
it("never overlays the native editor", () => {
expect(
composerSettingsToolbarLayout({ isExpanded: false, isVoicePresented: false }).overlayEditor,
).toBe(false);
expect(
composerSettingsToolbarLayout({ isExpanded: true, isVoicePresented: false }).overlayEditor,
).toBe(false);
expect(
composerSettingsToolbarLayout({ isExpanded: false, isVoicePresented: true }).overlayEditor,
).toBe(false);
});

it("mounts the row only when the draft is expanded or dictation is showing", () => {
expect(
composerSettingsToolbarLayout({ isExpanded: false, isVoicePresented: false }).mount,
).toBe(false);
expect(
composerSettingsToolbarLayout({ isExpanded: true, isVoicePresented: false }).mount,
).toBe(true);
expect(
composerSettingsToolbarLayout({ isExpanded: false, isVoicePresented: true }).mount,
).toBe(true);
});
});
15 changes: 15 additions & 0 deletions apps/mobile/src/features/threads/composer-draft-expansion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* The model chip is a RN Pressable. The composer is a UITextView. iOS native
* views win hit testing against overlapping RN siblings, so a chip laid out
* over the editor selects text / shows Paste instead of opening settings.
* Never overlay the settings row on the editor. Mount it in flow, or not at all.
*/
export function composerSettingsToolbarLayout(input: {
readonly isExpanded: boolean;
readonly isVoicePresented: boolean;
}): { readonly overlayEditor: boolean; readonly mount: boolean } {
return {
overlayEditor: false,
mount: input.isExpanded || input.isVoicePresented,
};
}
Loading