fix: mobile goal UX — bottom-sheet dialog, keyboard-safe target button, capacitor safe-top for overlays

- the goal dialog renders as the shared MobileOverlayPanel bottom sheet
  on mobile instead of a centered dialog
- tapping the target button to ARM keeps the soft keyboard open (the next
  message is the objective; same guard as the attachment/mic buttons),
  while opening the manage sheet lets the keyboard close as usual
- capacitor: bottom-sheet overlays cap their height by the keyboard inset
  AND the top safe area — with the keyboard raised while typing inside a
  sheet, 100dvh does not shrink (native resize is off) and the panel could
  slide under the notch/status bar; applies to every MobileOverlayPanel
This commit is contained in:
Bohdan Triapitsyn
2026-07-12 17:01:40 +03:00
parent 8d956f5f9c
commit 22d5ad3814
3 changed files with 52 additions and 6 deletions
@@ -18,6 +18,8 @@ import {
import { sessionGoalStatusColor, sessionGoalStatusLabelKey } from '@/lib/sessionGoalPresentation';
import { clearSessionGoal, setSessionGoal } from '@/lib/sessionGoalActions';
import { useI18n } from '@/lib/i18n';
import { MobileOverlayPanel } from '@/components/ui/MobileOverlayPanel';
import { useUIStore } from '@/stores/useUIStore';
interface SessionGoalDialogProps {
open: boolean;
@@ -31,6 +33,7 @@ interface SessionGoalDialogProps {
// (pause/resume/complete/clear) once a goal exists.
export function SessionGoalDialog({ open, onOpenChange, sessionId, directory }: SessionGoalDialogProps) {
const { t } = useI18n();
const isMobile = useUIStore((state) => state.isMobile);
const { goal } = useSessionGoal(sessionId, directory);
const objectiveContent = useGoalObjectiveContent(sessionId, goal);
@@ -86,13 +89,9 @@ export function SessionGoalDialog({ open, onOpenChange, sessionId, directory }:
true,
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>{goal ? t('chat.goal.dialog.titleManage') : t('chat.goal.dialog.titleCreate')}</DialogTitle>
</DialogHeader>
const title = goal ? t('chat.goal.dialog.titleManage') : t('chat.goal.dialog.titleCreate');
const body = (
<div className="space-y-3">
{goal && (
<div className="space-y-1 p-2 rounded-lg" style={{ backgroundColor: 'var(--surface-elevated)' }}>
@@ -193,6 +192,25 @@ export function SessionGoalDialog({ open, onOpenChange, sessionId, directory }:
</div>
</div>
</div>
);
// Mobile renders the shared bottom-sheet overlay instead of a centered
// dialog — same pattern as model controls and the session status panel.
if (isMobile) {
return (
<MobileOverlayPanel open={open} title={title} onClose={() => onOpenChange(false)}>
{body}
</MobileOverlayPanel>
);
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
</DialogHeader>
{body}
</DialogContent>
</Dialog>
);