fix(mobile): prevent menu tap from triggering unintended dashboard navigation

The mobile sidebar menu button redirects users to `cloudcli.ai/dashboard` when a
session was active. The redirect happened because
the menu was opened on `touchstart`, which mounted the sidebar before the touch
sequence completed; the follow-up tap/click then landed on the sidebar header
anchor.

This change rewrites mobile menu interaction handling in `MainContent.jsx` to
eliminate touch/click event leakage and ghost-click behavior.

Key changes:
- Added `suppressNextMenuClickRef` to guard against synthetic click events that
  fire after a touch interaction.
- Added `openMobileMenu(event)` helper to centralize `preventDefault`,
  `stopPropagation`, and `onMenuClick()` invocation.
- Added `handleMobileMenuTouchEnd(event)`:
  - opens the menu on `touchend` instead of `touchstart`
  - sets a short suppression window (350ms) for the next click.
- Added `handleMobileMenuClick(event)`:
  - ignores/suppresses click events during the suppression window
  - otherwise opens the menu normally.
- Updated all mobile menu button instances in `MainContent.jsx` (loading state,
  no-project state, active header state) to use:
  - `onTouchEnd={handleMobileMenuTouchEnd}`
  - `onClick={handleMobileMenuClick}`
- Removed the previous `onTouchStart` path that caused premature DOM mutation.

Behavioral impact:
- Mobile sidebar still opens reliably with one tap.
- Tap no longer leaks to newly-mounted sidebar header links.
- Prevents accidental redirects while preserving existing menu UX.
This commit is contained in:
Haileyesus
2026-02-07 16:58:47 +03:00
parent fdf2b09290
commit 8608d32dbd

View File

@@ -63,6 +63,7 @@ function MainContent({
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
const [editorExpanded, setEditorExpanded] = useState(false); const [editorExpanded, setEditorExpanded] = useState(false);
const resizeRef = useRef(null); const resizeRef = useRef(null);
const suppressNextMenuClickRef = useRef(false);
const { preferences } = useUiPreferences(); const { preferences } = useUiPreferences();
const { autoExpandTools, showRawParameters, showThinking, autoScrollToBottom, sendByCtrlEnter } = preferences; const { autoExpandTools, showRawParameters, showThinking, autoScrollToBottom, sendByCtrlEnter } = preferences;
@@ -164,6 +165,34 @@ function MainContent({
refreshTasks?.(); refreshTasks?.();
}; };
const openMobileMenu = (event) => {
if (event) {
event.preventDefault();
event.stopPropagation();
}
onMenuClick();
};
const handleMobileMenuTouchEnd = (event) => {
suppressNextMenuClickRef.current = true;
openMobileMenu(event);
window.setTimeout(() => {
suppressNextMenuClickRef.current = false;
}, 350);
};
const handleMobileMenuClick = (event) => {
if (suppressNextMenuClickRef.current) {
event.preventDefault();
event.stopPropagation();
return;
}
openMobileMenu(event);
};
// Handle resize functionality // Handle resize functionality
const handleMouseDown = (e) => { const handleMouseDown = (e) => {
if (isMobile) return; // Disable resize on mobile if (isMobile) return; // Disable resize on mobile
@@ -218,7 +247,8 @@ function MainContent({
className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0" className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0"
> >
<button <button
onClick={onMenuClick} onClick={handleMobileMenuClick}
onTouchEnd={handleMobileMenuTouchEnd}
className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button" className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -256,7 +286,8 @@ function MainContent({
className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0" className="bg-background border-b border-border p-2 sm:p-3 pwa-header-safe flex-shrink-0"
> >
<button <button
onClick={onMenuClick} onClick={handleMobileMenuClick}
onTouchEnd={handleMobileMenuTouchEnd}
className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button" className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 pwa-menu-button"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -297,11 +328,8 @@ function MainContent({
<div className="flex items-center space-x-2 min-w-0 flex-1"> <div className="flex items-center space-x-2 min-w-0 flex-1">
{isMobile && ( {isMobile && (
<button <button
onClick={onMenuClick} onClick={handleMobileMenuClick}
onTouchStart={(e) => { onTouchEnd={handleMobileMenuTouchEnd}
e.preventDefault();
onMenuClick();
}}
className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 touch-manipulation active:scale-95 pwa-menu-button flex-shrink-0" className="p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-700 touch-manipulation active:scale-95 pwa-menu-button flex-shrink-0"
> >
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -682,4 +710,4 @@ function MainContent({
); );
} }
export default React.memo(MainContent); export default React.memo(MainContent);