feat(chat): a new message shows on the pet; chat panels scroll one way only

A message from someone else in a room not on screen raises a card on the
pet (sender · group, the text or its files; click opens the room, gone after
10 s), plays the notification sound when notifications are on, and while
the tab is hidden shows a local system notification — never through the
server's push relay, which would reach other users' devices.

Group settings scrolled both ways (the name field and Rename overflowed, the
add-people list scrolled inside the panel): one vertical scroll now, the
field shrinks, the list grows in place. Every chat list hides horizontal
overflow and a long word or link wraps.
This commit is contained in:
2026-11-03 09:43:09 +07:00
parent 1dd2521183
commit 0d3e92cfda
8 changed files with 306 additions and 23 deletions
+21 -2
View File
@@ -103,7 +103,7 @@ import { startInFreeTabDetailed } from "../lanes/startSession";
import { workspaceHref } from "../SessionRowMenu";
import { Heart } from "lucide-react";
import { PetChat, petChatTool, type PetLocalReply } from "./PetChat";
import { ChatRail, ChatRoomView, NewChat } from "./ChatHub";
import { ChatNotice, ChatRail, ChatRoomView, NewChat } from "./ChatHub";
import { useChat } from "./useChat";
// Stroking: this many left/right turns of the pointer over the pet, within
@@ -247,6 +247,11 @@ function SagiBody() {
const people = useChat();
const [pane, setPane] = useState<"pet" | "new" | number>("pet");
const room = typeof pane === "number" ? people.rooms.find((r) => r.id === pane) : undefined;
// The room on screen: no notice for its messages.
const { setActive } = people;
useEffect(() => {
setActive(open && typeof pane === "number" ? pane : null);
}, [open, pane, setActive]);
// A room that went away (left, removed): back to the pet.
useEffect(() => {
if (typeof pane === "number" && people.rooms.length && !room) setPane("pet");
@@ -503,7 +508,21 @@ function SagiBody() {
</SagiFlyout>
)}
{!open && !menu && brain.bubble && (
{!open && !menu && people.notice && (
<SagiFlyout anchor={anchor}>
<ChatNotice
notice={people.notice}
onDismiss={people.dismissNotice}
onOpen={(roomId) => {
people.dismissNotice();
setPane(roomId);
setOpen(true);
}}
/>
</SagiFlyout>
)}
{!open && !menu && !people.notice && brain.bubble && (
<SagiFlyout anchor={anchor}>
<SpeechBubble
bubble={brain.bubble}