Sasquatch: fix desktop discoverability + repetitive greeting

- Move bubble & panel to bottom-RIGHT on desktop (was bottom-left, colliding
  with / hiding behind the desktop footer). Mobile stays bottom-left.
- Raise bubble/panel z-index to z-60 so it always sits above the footer.
- Re-clamp a stale saved drag position on mount: a bubble dragged off-screen
  on one device no longer disappears on another.
- Stop showing the long welcome greeting + action button on every open once a
  conversation has started (chatMessages > 0), so it no longer "says the same
  thing all over again".

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-08-27 15:51:42 +07:00
parent d12af9e56d
commit e996d96fa0

View file

@ -635,11 +635,26 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
const bubbleStyle = useMemo(() => {
if (dragPos) {
return { position: 'fixed' as const, left: dragPos.x, top: dragPos.y, bottom: 'auto', right: 'auto', zIndex: 50 };
return { position: 'fixed' as const, left: dragPos.x, top: dragPos.y, bottom: 'auto', right: 'auto', zIndex: 60 };
}
return undefined;
}, [dragPos]);
// Safety: a bubble position saved on one device/viewport (e.g. a phone) can
// land off-screen or hidden on another (e.g. desktop). On mount, if a saved
// drag position is outside the current viewport, clear it so the bubble
// returns to its default, always-visible corner.
useEffect(() => {
if (!dragPos) return;
const maxX = window.innerWidth - 56;
const maxY = window.innerHeight - 56;
if (dragPos.x < 0 || dragPos.y < 0 || dragPos.x > maxX || dragPos.y > maxY) {
setDragPos(null);
}
// Only run on mount — we don't want to fight the user mid-drag.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Only auto-open the panel when the context message explicitly opts in via
// `autoShow`. On the map (`/`) and The Lab (`/lab`) the guide stays minimized
// until the user taps the bubble — the message content is still used to
@ -1181,8 +1196,11 @@ Instructions:
<div
ref={dragRef}
className={cn(
'z-50 touch-none select-none',
dragPos ? '' : 'fixed bottom-[9.5rem] left-4 md:bottom-6 md:left-4',
'z-[60] touch-none select-none',
// Mobile: bottom-left, clear of the bottom nav. Desktop: bottom-RIGHT
// so the bubble is easy to spot and never hides behind the footer
// (which is bottom-left on desktop).
dragPos ? '' : 'fixed bottom-[9.5rem] left-4 md:bottom-6 md:left-auto md:right-6',
)}
style={bubbleStyle}
onPointerDown={onPointerDown}
@ -1217,13 +1235,13 @@ Instructions:
{shouldShowPanel && !isMinimized && (
<div
className={cn(
'fixed z-50 flex flex-col transition-all duration-300 ease-out',
'fixed z-[60] flex flex-col transition-all duration-300 ease-out',
// Mobile: left-anchored, max ~65% width — leaves right side free for Quick Snap + filter pills.
// Height is bounded to the visible viewport (dvh) with safe-area insets so the
// header/close button never slide off the top of the screen behind the status bar.
'bottom-[4.5rem] left-2 w-[min(calc(100vw-5rem),340px)]',
// Desktop: compact panel bottom-left
'md:bottom-6 md:left-4 md:w-[340px]',
// Desktop: compact panel bottom-RIGHT (matches the bubble, clears the footer)
'md:bottom-6 md:left-auto md:right-6 md:w-[360px]',
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-3 pointer-events-none'
)}
style={{
@ -1305,13 +1323,17 @@ Instructions:
{/* Body — scrolls internally so the card never outgrows the viewport */}
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-3">
{/* Greeting */}
<p className="text-xs text-gray-300 leading-relaxed mb-3">
{activeMessage.message}
</p>
{/* Greeting — only shown before the user starts chatting, so the
same wall of welcome text doesn't reappear on every open once
a conversation is underway. */}
{chatMessages.length === 0 && (
<p className="text-xs text-gray-300 leading-relaxed mb-3">
{activeMessage.message}
</p>
)}
{/* Action button */}
{activeMessage.action && (
{/* Action button — also only before chatting */}
{chatMessages.length === 0 && activeMessage.action && (
<button
onClick={activeMessage.action.onClick}
className="w-full py-2 px-3 rounded-lg text-xs font-bold text-black bg-[#ccff00] hover:bg-[#d4ff1a] transition-colors mb-3"