v4: a chat mention notification opens the chat on the mentioned message, so the newest one clears the chat badge — it linked to ?chat=open, and the chat opened on the "new" divider whenever the unread messages didn't fit. That left the list above the bottom, so the read marker never moved and the badge stayed, while the mention itself was out of sight. It now links to ?chat=<message id>: ChatProvider opens on any ?chat= (open, or a message id passed to ChatModal as focusId), and ChatModal opens centred on that message and flashes it. When the message is within a screen of the bottom, the list stays at the bottom and reads everything, as it always has. An older mention still leaves the messages after it unread. A message outside the first page of history falls back to the divider. ChatModal's flash is now its own callback, shared with jumping to a message. 675 frontend tests pass, and tsc, eslint, deno fmt and doc:lint are clean; reproduced and checked in the app with 36 unread mentions from a second account: the newest clears the badge, an older one opens on it with the badge kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,9 +32,9 @@ export interface ChatProviderProps {
|
||||
|
||||
/**
|
||||
* Provides {@link ChatContext} and renders the one chat modal while it is
|
||||
* open. `openChat` opens it, and so does a `?chat=open` query parameter on any
|
||||
* page, which is where a chat mention notification links. It stays closed for
|
||||
* visitors.
|
||||
* open. `openChat` opens it, and so does a `?chat=` query parameter on any
|
||||
* page: `?chat=open`, or `?chat=<message id>` to open on that message, which is
|
||||
* where a chat mention notification links. It stays closed for visitors.
|
||||
*/
|
||||
export function ChatProvider(
|
||||
{ children }: ChatProviderProps,
|
||||
@@ -43,14 +43,14 @@ export function ChatProvider(
|
||||
const [manuallyOpen, setManuallyOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const deepLinkOpen = searchParams.get("chat") === "open";
|
||||
const isChatOpen = !!user && (manuallyOpen || deepLinkOpen);
|
||||
const deepLink = searchParams.get("chat");
|
||||
const isChatOpen = !!user && (manuallyOpen || !!deepLink);
|
||||
|
||||
const openChat = useCallback(() => setManuallyOpen(true), []);
|
||||
const closeChat = useCallback(() => {
|
||||
setManuallyOpen(false);
|
||||
// Or a reload would open the chat again.
|
||||
if (searchParams.get("chat") === "open") {
|
||||
if (searchParams.has("chat")) {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.delete("chat");
|
||||
setSearchParams(next, { replace: true });
|
||||
@@ -64,7 +64,10 @@ export function ChatProvider(
|
||||
{children}
|
||||
{isChatOpen && (
|
||||
<Suspense>
|
||||
<ChatModal onClose={closeChat} />
|
||||
<ChatModal
|
||||
onClose={closeChat}
|
||||
focusId={deepLink && deepLink !== "open" ? deepLink : undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</ChatContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user