Skip to content

Commit c84cab5

Browse files
committed
feat(command): add channels to cmd-k and show task filing
Channels were only reachable by clicking the sidebar. Surface them in the command palette as a searchable "Channels" section that navigates to /website/$channelId, and auto-expand the channel in the sidebar when its route is active. Task entries now show the channel they're filed to as a muted "· #channel" suffix (searchable by channel name). Generated-By: PostHog Code Task-Id: bc6e7269-db09-4ef7-b3a4-41f0ca03abaf
1 parent 2662157 commit c84cab5

5 files changed

Lines changed: 109 additions & 15 deletions

File tree

packages/shared/src/analytics-events.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export type CommandMenuAction =
4747
| "toggle-theme"
4848
| "toggle-left-sidebar"
4949
| "open-review-panel"
50-
| "open-task";
50+
| "open-task"
51+
| "open-channel";
5152

5253
// Event property interfaces
5354
export interface TaskListViewProperties {

packages/ui/src/features/canvas/components/ChannelsList.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ import { toast } from "@posthog/ui/primitives/toast";
5757
import * as Collapsible from "@radix-ui/react-collapsible";
5858
import { Box, Flex, IconButton, Text, Tooltip } from "@radix-ui/themes";
5959
import { useNavigate, useRouterState } from "@tanstack/react-router";
60-
import { type ReactNode, useState } from "react";
60+
import { type ReactNode, useEffect, useState } from "react";
6161
import { hostClient } from "../hostClient";
6262

6363
function NavButton({
@@ -338,8 +338,14 @@ function ChannelSection({
338338
const { data: tasks } = useTasks();
339339
const { tasks: filedTasks } = useChannelTasks(channel.id);
340340
const base = `/website/${channel.id}`;
341-
// Channels always start collapsed on load; expansion is session-only.
342-
const [open, setOpen] = useState(false);
341+
const isActive = pathname === base || pathname.startsWith(`${base}/`);
342+
// Channels start collapsed; expansion is session-only. Navigating into a
343+
// channel (sidebar, cmd-k, deep link) auto-expands it so the active channel
344+
// is always open, while leaving manual collapse/expand intact afterward.
345+
const [open, setOpen] = useState(isActive);
346+
useEffect(() => {
347+
if (isActive) setOpen(true);
348+
}, [isActive]);
343349

344350
return (
345351
<Box className="group/chan relative">
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useHostTRPC } from "@posthog/host-router/react";
2+
import {
3+
type Channel,
4+
useChannels,
5+
} from "@posthog/ui/features/canvas/hooks/useChannels";
6+
import { useQueries } from "@tanstack/react-query";
7+
import { useMemo } from "react";
8+
9+
/**
10+
* Map of taskId → the channel it's filed to. A task is filed to at most one
11+
* channel (ChannelTasksService moves the row rather than duplicating it), so
12+
* the mapping is unambiguous. Fans out one `channelTasks.list` query per
13+
* channel; results are shared with the channel sidebar's per-section queries
14+
* through the react-query cache.
15+
*/
16+
export function useTaskChannelMap(options?: {
17+
enabled?: boolean;
18+
}): Map<string, Channel> {
19+
const enabled = options?.enabled ?? true;
20+
const { channels } = useChannels({ enabled });
21+
const trpc = useHostTRPC();
22+
const results = useQueries({
23+
queries: channels.map((channel) =>
24+
trpc.channelTasks.list.queryOptions(
25+
{ channelId: channel.id },
26+
{ enabled, staleTime: 5_000 },
27+
),
28+
),
29+
});
30+
return useMemo(() => {
31+
const map = new Map<string, Channel>();
32+
results.forEach((res, i) => {
33+
const channel = channels[i];
34+
if (!channel) return;
35+
for (const record of res.data ?? []) {
36+
if (record.taskId) map.set(record.taskId, channel);
37+
}
38+
});
39+
return map;
40+
}, [results, channels]);
41+
}

packages/ui/src/features/command/CommandMenu.tsx

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HashIcon } from "@phosphor-icons/react";
12
import {
23
Autocomplete,
34
AutocompleteCollection,
@@ -15,6 +16,8 @@ import {
1516
type CommandMenuAction,
1617
} from "@posthog/shared/analytics-events";
1718
import type { Task } from "@posthog/shared/domain-types";
19+
import { useChannels } from "@posthog/ui/features/canvas/hooks/useChannels";
20+
import { useTaskChannelMap } from "@posthog/ui/features/canvas/hooks/useTaskChannelMap";
1821
import { useReviewNavigationStore } from "@posthog/ui/features/code-review/reviewNavigationStore";
1922
import { CommandKeyHints } from "@posthog/ui/features/command/CommandKeyHints";
2023
import { useFolders } from "@posthog/ui/features/folders/useFolders";
@@ -26,6 +29,7 @@ import { TaskIcon } from "@posthog/ui/features/sidebar/components/items/TaskIcon
2629
import { useSidebarStore } from "@posthog/ui/features/sidebar/sidebarStore";
2730
import { useTaskPrStatus } from "@posthog/ui/features/sidebar/useTaskPrStatus";
2831
import { useTasks } from "@posthog/ui/features/tasks/useTasks";
32+
import { navigateToChannel } from "@posthog/ui/router/navigationBridge";
2933
import { useAppView } from "@posthog/ui/router/useAppView";
3034
import { openTask, openTaskInput } from "@posthog/ui/router/useOpenTask";
3135
import { track } from "@posthog/ui/shell/analytics";
@@ -49,6 +53,8 @@ interface CommandMenuProps {
4953
type Command = {
5054
id: string;
5155
label: string;
56+
/** Muted trailing detail shown after a middot, e.g. a task's channel. */
57+
detail?: string;
5258
keywords?: string;
5359
icon: React.ReactNode;
5460
action: CommandMenuAction;
@@ -89,6 +95,8 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
8995
const openSettingsDialog = openSettings;
9096
const closeSettingsDialog = closeSettings;
9197
const { folders } = useFolders();
98+
const { channels } = useChannels();
99+
const taskChannelMap = useTaskChannelMap({ enabled: open });
92100
const { theme, setTheme } = useThemeStore();
93101
const toggleLeftSidebar = useSidebarStore((state) => state.toggle);
94102
const view = useAppView();
@@ -254,24 +262,50 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
254262
return [
255263
{
256264
label: "Tasks",
257-
items: tasks.map((task) => ({
258-
id: `task-${task.id}`,
259-
label: task.title,
260-
icon: <TaskCommandIcon task={task} />,
261-
action: "open-task" as CommandMenuAction,
265+
items: tasks.map((task) => {
266+
const channel = taskChannelMap.get(task.id);
267+
return {
268+
id: `task-${task.id}`,
269+
label: task.title,
270+
detail: channel?.name,
271+
// Include the channel name so searching it surfaces filed tasks.
272+
keywords: channel?.name,
273+
icon: <TaskCommandIcon task={task} />,
274+
action: "open-task" as CommandMenuAction,
275+
onRun: () => {
276+
closeSettingsDialog();
277+
void openTask(task);
278+
},
279+
};
280+
}),
281+
},
282+
];
283+
}, [tasks, taskChannelMap, closeSettingsDialog]);
284+
285+
const channelSections = useMemo<CommandSection[]>(() => {
286+
if (channels.length === 0) return [];
287+
return [
288+
{
289+
label: "Channels",
290+
items: channels.map((channel) => ({
291+
id: `channel-${channel.id}`,
292+
label: channel.name,
293+
keywords: "channel",
294+
icon: <HashIcon size={12} className="text-gray-11" />,
295+
action: "open-channel" as CommandMenuAction,
262296
onRun: () => {
263297
closeSettingsDialog();
264-
void openTask(task);
298+
navigateToChannel(channel.id);
265299
},
266300
})),
267301
},
268302
];
269-
}, [tasks, closeSettingsDialog]);
303+
}, [channels, closeSettingsDialog]);
270304

271-
// Commands and tasks share a single filterable list.
305+
// Commands, channels, and tasks share a single filterable list.
272306
const sections = useMemo(
273-
() => [...commandSections, ...taskSections],
274-
[commandSections, taskSections],
307+
() => [...commandSections, ...channelSections, ...taskSections],
308+
[commandSections, channelSections, taskSections],
275309
);
276310

277311
const allCommands = useMemo(
@@ -314,7 +348,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
314348
}}
315349
>
316350
<AutocompleteInput
317-
placeholder="Search commands and tasks…"
351+
placeholder="Search commands, channels, and tasks…"
318352
autoFocus
319353
showClear
320354
/>
@@ -343,6 +377,11 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
343377
<span className="wrap-break-word min-w-0 whitespace-normal">
344378
{cmd.label}
345379
</span>
380+
{cmd.detail && (
381+
<span className="shrink-0 text-gray-9">
382+
· #{cmd.detail}
383+
</span>
384+
)}
346385
</AutocompleteItem>
347386
)}
348387
</AutocompleteCollection>

packages/ui/src/router/navigationBridge.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ export function navigateToTaskPending(key: string): void {
3232
});
3333
}
3434

35+
export function navigateToChannel(channelId: string): void {
36+
void getRouterOrNull()?.navigate({
37+
to: "/website/$channelId",
38+
params: { channelId },
39+
});
40+
}
41+
3542
export function navigateToFolderSettings(folderId: string): void {
3643
void getRouterOrNull()?.navigate({
3744
to: "/folders/$folderId",

0 commit comments

Comments
 (0)