Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/buzz-help.md
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,9 @@ for 30 days expires.
**Open Activity** opens the page. The card's **Unread only** switch is
separate from the Activity page's filters and hides reactions when on. The preview is not shown on touch
devices or while the Activity page is open.
- **Channel activity**: rest the pointer on a channel or Solo chat in the
sidebar to see its working agents and **Unread threads**. Choosing an unread
thread opens it at the reply shown, expanding the replies it is nested under.
- **Sidebar width**: drag the sidebar's right edge, or focus its resize handle
and use the Left/Right arrow keys. Double-click the handle to reset the width.
Messages and plugin pages share the saved width within the same community
Expand Down
6 changes: 4 additions & 2 deletions src/bundled/channels/ChannelSidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ type ItemProps = {
working: boolean;
selected: string | undefined;
onSelect: (id: string) => void;
onOpenThread: (channelId: string, rootId: string) => void;
onOpenThread: (channelId: string, messageId: string, rootId: string) => void;
onOpenMessage: (channelId: string, messageId: string) => void;
onOpenWorkingAgent?: (
channelId: string,
Expand Down Expand Up @@ -268,7 +268,9 @@ function ChannelSidebarItemCore({
agentProfiles={agentProfiles}
{...(onOpenWorkingAgent ? { onOpenWorkingAgent } : {})}
{...(onOpenAgentActivity ? { onOpenAgentActivity } : {})}
onOpenThread={(item) => onOpenThread(item.channelId, item.rootId)}
onOpenThread={(item) =>
onOpenThread(item.channelId, item.latestMessageId, item.rootId)
Comment thread
matt2e marked this conversation as resolved.
Resolved
}
onOpenMention={(item) =>
onOpenMessage(item.channelId, item.messageId)
}
Expand Down
7 changes: 7 additions & 0 deletions src/bundled/channels/ChannelsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Avatar } from "../../shared/design-system/ui/Avatar";
import { useToastNotification } from "../../shared/design-system/ui/Toast";
import { useReturnHighlight } from "../../features/messages/use-return-highlight";
import { useMessageOpening } from "../../features/messages/use-message-opening";
import { beginReveal } from "../../features/messages/use-message-reveal";
import { ChannelBody } from "./ChannelBody";
import {
entryPane,
Expand Down Expand Up @@ -677,6 +678,12 @@ function ChannelWorkspace({
navigation?.target.kind === "conversation"
? navigation.target.threadRootId
: undefined;
useLayoutEffect(() => {
// The message's reveal waits for its channel, timeline or thread to load;
// focus the person moves meanwhile counts from now.
if (requestedMessage && navigation && !navigation.signal.aborted)
beginReveal(navigation.signal);
}, [requestedMessage, navigation]);
const membersRequested =
navigation?.target.kind === "conversation" &&
navigation.target.panel === "members";
Expand Down
64 changes: 64 additions & 0 deletions src/features/channel-navigation/ChannelInventory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,70 @@ it("copies a channel link that opens the same channel", async () => {
).toBeInTheDocument();
});

it("opens an Activity thread row at the reply it previews, in its thread", async () => {
const root = "d".repeat(64);
const reply = "e".repeat(64);
const h = fixture();
const item = {
channelId: "beta",
rootId: root,
latestMessageId: reply,
authorId: "b".repeat(64),
preview: "Nested reply",
unreadCount: 1,
createdAt: 1,
};
// Stable per channel, as external-store snapshots must be.
const snapshots = new Map(
["alpha", "beta", "gamma"].map((id) => [
id,
{
channelId: id,
items: id === "beta" ? [item] : [],
coverage: "observed" as const,
freshness: "observed" as const,
},
]),
);
const unread = h.session.unread;
h.session.unread = {
...unread,
activity: (id) => snapshots.get(id) ?? unread.activity(id),
subscribeActivity: () => () => {},
};
// The sidebar reads back the attempt it started to hand off focus return.
let opened: unknown;
vi.mocked(h.navigator.open).mockImplementation(async (next) => {
opened = next;
return { status: "opened" };
});
Object.assign(h.navigator, {
snapshot: () => ({
attempt: {
id: "visit",
entry: { id: "entry", target: opened },
signal: new AbortController().signal,
},
}),
});
render(h.view("alpha"));
const beta = await screen.findByRole("button", { name: /^beta/ });
beta.focus();
await userEvent.keyboard("{Enter}");
const popup = await screen.findByRole("dialog", { name: "Activity in beta" });
await userEvent.click(
within(popup).getByRole("button", { name: /^Open unread thread from/ }),
);
expect(h.navigator.open).toHaveBeenLastCalledWith(
expect.objectContaining({
kind: "conversation",
channelId: "beta",
messageId: reply,
threadRootId: root,
}),
);
});

it("offers DMs a Move conversation menu and relocates them into a saved group", async () => {
const saved: SidebarPreferences = {
sections: [{ id: "work", name: "Work", order: 0 }],
Expand Down
7 changes: 1 addition & 6 deletions src/features/channel-navigation/ChannelInventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,6 @@ export function ChannelInventory({
},
[viewer, relay, queries, handoff, sidebar.list, navigator, scope],
);
const openActivityThread = useCallback(
(channelId: string, rootId: string) =>
openActivityMessage(channelId, rootId, rootId),
[openActivityMessage],
);
const openWorkingAgent = useCallback(
(channelId: string, _agent: string, messageId: string | undefined) => {
const root =
Expand Down Expand Up @@ -1543,7 +1538,7 @@ export function ChannelInventory({
working={workingChannels.has(channel.id)}
selected={composingMessage ? undefined : selected}
onSelect={select}
onOpenThread={openActivityThread}
onOpenThread={openActivityMessage}
onOpenMessage={openActivityMessage}
onOpenWorkingAgent={openWorkingAgent}
onOpenAgentActivity={openAgentActivity}
Expand Down
4 changes: 3 additions & 1 deletion src/features/channel-navigation/SoloInventory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,13 @@ it("offers unread thread navigation and mark-read through the shared activity po
name: "Open unread thread from Owned Agent: Needs review",
}),
);
// The row opens the reply it previews, in its thread, not the thread root.
expect(f.open).toHaveBeenLastCalledWith(
expect.objectContaining({
kind: "conversation",
channelId: id,
messageId: root,
messageId: "e".repeat(64),
threadRootId: root,
}),
);
});
Expand Down
8 changes: 5 additions & 3 deletions src/features/channel-navigation/SoloInventory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,12 @@ export function SoloInventory({
[navigator, scope],
);
const openMessage = useCallback(
(channelId: string, messageId?: string) => {
(channelId: string, messageId?: string, threadRootId?: string) => {
if (handoff && handoff.session !== session) return;
const destination =
messageId && session.viewer
? buzzLinkTarget(
`buzz://message?channel=${channelId}&id=${messageId}`,
`buzz://message?channel=${channelId}&id=${messageId}${threadRootId ? `&thread=${threadRootId}` : ""}`,
{
viewer: session.viewer,
communityOrigin: scope.slice(0, -(session.viewer.length + 1)),
Expand Down Expand Up @@ -224,7 +224,9 @@ export function SoloInventory({
Icon={ChatIcon}
onSelect={() => void open(item.id)}
onRename={rename}
onOpenThread={(thread) => openMessage(thread.channelId, thread.rootId)}
onOpenThread={(thread) =>
openMessage(thread.channelId, thread.latestMessageId, thread.rootId)
}
onOpenMention={(mention) =>
openMessage(mention.channelId, mention.messageId)
}
Expand Down
Loading
Loading