diff --git a/desktop/src/components/chat/ChatInput.test.tsx b/desktop/src/components/chat/ChatInput.test.tsx index 992224fd..8b59c484 100644 --- a/desktop/src/components/chat/ChatInput.test.tsx +++ b/desktop/src/components/chat/ChatInput.test.tsx @@ -72,14 +72,14 @@ vi.mock('../controls/PermissionModeSelector', () => ({ vi.mock('../controls/ModelSelector', async () => { const React = await vi.importActual('react') return { - ModelSelector: React.forwardRef<{ open: () => void }, Record>((_props, ref) => { + ModelSelector: React.forwardRef<{ open: () => void }, { fluid?: boolean }>(({ fluid }, ref) => { const [open, setOpen] = React.useState(false) React.useImperativeHandle(ref, () => ({ open: () => setOpen(true) }), []) return ( - <> +
{open &&
Model selector opened
} - +
) }), } @@ -1380,6 +1380,9 @@ describe('ChatInput file mentions', () => { expect(screen.getByTestId('chat-input-shell').className).toContain('safe-area-inset-bottom') expect(screen.getByTestId('chat-input-panel')).toHaveClass('rounded-2xl') expect(screen.getByTestId('chat-input-panel')).not.toHaveClass('rounded-b-none') + expect(screen.getByTestId('chat-input-toolbar-leading')).toHaveClass('shrink-0', 'gap-1') + expect(screen.getByTestId('chat-input-toolbar-trailing')).toHaveClass('min-w-0', 'flex-1', 'justify-end', 'gap-1') + expect(screen.getByTestId('model-selector-shell')).toHaveClass('min-w-0', 'flex-1') fireEvent.change(screen.getByRole('textbox'), { target: { value: '@cond', selectionStart: 5 }, diff --git a/desktop/src/components/chat/ChatInput.tsx b/desktop/src/components/chat/ChatInput.tsx index 39c86bc5..695a4b4b 100644 --- a/desktop/src/components/chat/ChatInput.tsx +++ b/desktop/src/components/chat/ChatInput.tsx @@ -1269,9 +1269,12 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro
-
+
{!isMemberSession && ( <>
@@ -1308,7 +1311,10 @@ export function ChatInput({ variant = 'default', compact = false }: ChatInputPro )}
-
+
{!isMemberSession && activeTabId && ( )} {!isMemberSession && activeTabId && ( - + )}
{expanded ? ( diff --git a/desktop/src/lib/touchH5.test.ts b/desktop/src/lib/touchH5.test.ts index 77911cc9..ca37f548 100644 --- a/desktop/src/lib/touchH5.test.ts +++ b/desktop/src/lib/touchH5.test.ts @@ -419,8 +419,9 @@ describe('touch-H5 stylesheet contract', () => { expect(css).toMatch(/html\[data-touch-h5\]\[data-touch-h5-keyboard\] \.app-shell-viewport \{\s*\n\s*padding-bottom: 0px;/) }) - it('keeps message action bars always visible on touch', () => { - expect(css).toMatch(/html\[data-touch-h5\] \[data-message-actions\] \{\s*\n\s*opacity: 1;\s*\n\s*pointer-events: auto;/) + it('keeps message action bars visible in the mobile shell without relying on touch detection', () => { + expect(css).toMatch(/\.app-shell--mobile \[data-message-actions\],\s*\nhtml\[data-touch-h5\] \[data-message-actions\] \{\s*\n\s*opacity: 1;\s*\n\s*pointer-events: auto;/) + expect(css).toMatch(/\.app-shell--mobile \[data-message-actions\] button \{\s*\n\s*width: 2\.5rem;/) }) it('disables paint skipping for the trace-window rows too', () => { diff --git a/desktop/src/pages/ActiveSession.tsx b/desktop/src/pages/ActiveSession.tsx index d73f9d79..7bbe5c21 100644 --- a/desktop/src/pages/ActiveSession.tsx +++ b/desktop/src/pages/ActiveSession.tsx @@ -678,7 +678,7 @@ export function ActiveSession() { {historyError}
) : ( - + )} )} diff --git a/desktop/src/theme/globals.css b/desktop/src/theme/globals.css index 81254650..9d702735 100644 --- a/desktop/src/theme/globals.css +++ b/desktop/src/theme/globals.css @@ -1610,13 +1610,29 @@ html[data-touch-h5][data-touch-h5-keyboard] .app-shell-viewport { padding-bottom: 0px; } -/* Message action bars (copy / branch) are hover-revealed on desktop; touch - devices have no hover, so keep them always visible there. */ +/* Message action bars (copy / branch) are hover-revealed on desktop. Keep + them visible whenever the browser is in the narrow H5 shell, even if an + embedded browser reports no coarse pointer, and retain the touch marker as + coverage for wider tablets. */ +.app-shell--mobile [data-message-actions], html[data-touch-h5] [data-message-actions] { opacity: 1; pointer-events: auto; } +.app-shell--mobile [data-message-actions] { + height: 2.5rem; +} + +.app-shell--mobile [data-message-actions] > div, +.app-shell--mobile [data-message-actions] button { + min-height: 2.5rem; +} + +.app-shell--mobile [data-message-actions] button { + width: 2.5rem; +} + /* Kill the gray flash WKWebView paints over any tapped interactive element; components carry their own pressed/hover styling. */ html[data-touch-h5],