Skip to content
Merged
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
27 changes: 5 additions & 22 deletions packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
BrainIcon,
HashIcon,
HouseIcon,
PlugsConnectedIcon,
RobotIcon,
Expand All @@ -24,16 +25,15 @@ import {
} from "@posthog/shared";
import { channelSectionFor } from "@posthog/ui/features/canvas/channelSections";
import { iconForTemplate } from "@posthog/ui/features/canvas/components/canvasTemplateIcon";
import { ensurePersonalChannel } from "@posthog/ui/features/canvas/ensurePersonalChannel";
import {
type Channel,
useChannelMutations,
useChannels,
} from "@posthog/ui/features/canvas/hooks/useChannels";
import {
useDashboard,
useDashboards,
} from "@posthog/ui/features/canvas/hooks/useDashboards";
import { PERSONAL_CHANNEL_NAME } from "@posthog/ui/features/canvas/hooks/useTaskChannels";
import { SHORTCUTS } from "@posthog/ui/features/command/keyboard-shortcuts";
import { useFeatureFlag } from "@posthog/ui/features/feature-flags/useFeatureFlag";
import { usePanelLayoutStore } from "@posthog/ui/features/panels/panelLayoutStore";
Expand All @@ -50,7 +50,6 @@ import {
useRouter,
useRouterState,
} from "@tanstack/react-router";
import { SquircleDashed } from "lucide-react";
import { type ReactNode, useEffect, useMemo } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import {
Expand Down Expand Up @@ -87,13 +86,6 @@ declare module "@tanstack/history" {
const canvasInfo = new Map<string, { name: string; templateId: string }>();
const taskInfo = new Map<string, string>();

// Dedupe concurrent #me provisioning. The folder-creation endpoint isn't
// server-side idempotent, and the new-tab path is on Cmd+T (trivially
// double-fired/held) — so two landings racing before the first create's cache
// update lands could each create a "me" folder. One in-flight create is shared
// across callers until it settles.
let personalChannelInFlight: Promise<Channel> | null = null;

/** Bounded insert (most-recent kept) so the caches don't grow unbounded over a
* long session. */
const MAX_CACHE_ENTRIES = 200;
Expand Down Expand Up @@ -525,8 +517,8 @@ export function BrowserTabStrip() {
const meta = channelSectionFor(section);
return {
id: t.id,
label: meta?.label ?? channel ?? "Context",
icon: <SquircleDashed size={14} />,
label: meta?.label ?? channel ?? "Channel",
icon: <HashIcon size={14} />,
channelName: channel,
// No section meta → the channel's index page.
isChannelHome: !meta,
Expand Down Expand Up @@ -739,16 +731,7 @@ export function BrowserTabStrip() {
// row uses); fall back to the new-task screen if it can't be created.
void (async () => {
try {
const existing = channels.find((c) => c.name === PERSONAL_CHANNEL_NAME);
if (!existing && !personalChannelInFlight) {
personalChannelInFlight = createChannel(
PERSONAL_CHANNEL_NAME,
).finally(() => {
personalChannelInFlight = null;
});
}
const folder = existing ?? (await personalChannelInFlight);
if (!folder) return;
const folder = await ensurePersonalChannel(channels, createChannel);
navigate({
to: "/website/$channelId",
params: { channelId: folder.id },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export function ActivityView() {
Activity
</Text>
<Text size="2" className="block text-muted-foreground">
Mentions of you across contexts.
Mentions of you across channels.
</Text>
<div className="mt-4">
{isLoading && items.length === 0 ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { HashIcon } from "@phosphor-icons/react";
import {
Button,
Tooltip,
Expand All @@ -7,7 +8,6 @@ import {
import { HeaderTitleEditor } from "@posthog/ui/features/task-detail/HeaderTitleEditor";
import { Flex, Text } from "@radix-ui/themes";
import { useNavigate } from "@tanstack/react-router";
import { SquircleDashed } from "lucide-react";
import { type ReactNode, useState } from "react";

interface ChannelBreadcrumbProps {
Expand Down Expand Up @@ -48,10 +48,7 @@ export function ChannelBreadcrumb({

const channelSegment = (
<>
<SquircleDashed
size={12}
className="mt-px shrink-0 text-muted-foreground/80"
/>
<HashIcon size={12} className="mt-px shrink-0 text-muted-foreground/80" />
<Text
className="min-w-0 truncate whitespace-nowrap font-medium text-[13px]"
title={channelName}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe("ChannelContextPanel", () => {
const user = userEvent.setup();
const onClose = renderPanel();
await user.click(
screen.getByRole("button", { name: "Close context panel" }),
screen.getByRole("button", { name: "Close CONTEXT.md panel" }),
);
expect(onClose).toHaveBeenCalledTimes(1);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function ChannelContextPanel({
<button
type="button"
onClick={onClose}
aria-label="Close context panel"
aria-label="Close CONTEXT.md panel"
className="flex size-6 shrink-0 items-center justify-center rounded text-gray-10 hover:bg-gray-4 hover:text-gray-12"
>
<X size={14} />
Expand Down
9 changes: 3 additions & 6 deletions packages/ui/src/features/canvas/components/ChannelHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { HashIcon } from "@phosphor-icons/react";
import { Button, cn } from "@posthog/quill";
import { ChannelTabs } from "@posthog/ui/features/canvas/components/ChannelTabs";
import { useChannels } from "@posthog/ui/features/canvas/hooks/useChannels";
import { Text } from "@radix-ui/themes";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { SquircleDashed } from "lucide-react";

// The shared channel header: a clickable "# channel" that doubles as the Home
// item — it routes to the channel home (`/website/$channelId`, like the sidebar
Expand All @@ -29,12 +29,9 @@ export function ChannelHeader({ channelId }: { channelId: string }) {
size="sm"
className={cn("min-w-0", isHome ? "bg-fill-selected" : "")}
>
<SquircleDashed
size={20}
className="shrink-0 text-muted-foreground/80"
/>
<HashIcon size={20} className="shrink-0 text-muted-foreground/80" />
<Text className="min-w-0 truncate font-medium" title={channelName}>
{channelName ?? "Context"}
{channelName ?? "Channel"}
</Text>
</Button>
<ChannelTabs channelId={channelId} />
Expand Down
11 changes: 6 additions & 5 deletions packages/ui/src/features/canvas/components/ChannelIntro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export function ChannelIntro({
<span className={mentionChipClass}>
@{userDisplayName(creator ?? null)}
</span>{" "}
created this context {creationDatePhrase(channel.created_at)}. This
created this channel {creationDatePhrase(channel.created_at)}. This
is the very beginning of the{" "}
<Text weight="bold">{channelName}</Text> context.
<Text weight="bold">{channelName}</Text> channel.
</Text>
)}
</div>
Expand All @@ -70,7 +70,7 @@ export function ChannelIntro({
<ItemContent className="self-start">
<ItemTitle>Created context.md</ItemTitle>
<ItemDescription className="text-xs">
Used in all sessions within this context
Used in all sessions within this channel
</ItemDescription>
</ItemContent>
</Item>
Expand Down Expand Up @@ -125,9 +125,10 @@ export function ChannelIntro({
<Info size={18} />
</ItemMedia>
<ItemContent className="self-start">
<ItemTitle>Learn more about contexts</ItemTitle>
<ItemTitle>Learn more about channels</ItemTitle>
<ItemDescription className="text-xs">
Context is a group of tasks that are related to a specific topic.
A channel is a group of tasks that are related to a specific
topic.
</ItemDescription>
</ItemContent>
</Item>
Expand Down
71 changes: 71 additions & 0 deletions packages/ui/src/features/canvas/components/ChannelsFab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { FileTextIcon, HashIcon, PlusIcon } from "@phosphor-icons/react";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@posthog/quill";
import { CreateChannelModal } from "@posthog/ui/features/canvas/components/CreateChannelModal";
import { openTaskInput } from "@posthog/ui/router/useOpenTask";
import { useRouterState } from "@tanstack/react-router";
import { useState } from "react";

// The create affordance for the Channels space, floated over the bottom-right
// of the channel list. It owns the create-channel modal (the list itself has no
// other entry point) and opens its menu upward, since it sits at the bottom.
export function ChannelsFab() {
const [modalOpen, setModalOpen] = useState(false);
// New task has no /website mirror yet, so it jumps back to Code unless we're
// already in the Channels space — same rule as the nav's New task row.
const inChannels = useRouterState({
select: (s) => s.location.pathname.startsWith("/website"),
});

return (
<>
<DropdownMenu>
<Tooltip>
<TooltipTrigger
render={
<DropdownMenuTrigger
render={
<Button
variant="primary"
size="icon-lg"
aria-label="Create"
className="absolute right-3 bottom-3 z-10 rounded-full shadow-lg"
>
<PlusIcon size={20} weight="bold" />
</Button>
}
/>
}
/>
<TooltipContent side="top" align="center">
Create something new
</TooltipContent>
</Tooltip>
<DropdownMenuContent align="center" side="top" sideOffset={6}>
<DropdownMenuItem onClick={() => setModalOpen(true)}>
<HashIcon size={14} className="text-gray-9" />
New channel
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
openTaskInput(inChannels ? { space: "website" } : undefined)
}
>
<FileTextIcon size={14} className="text-gray-9" />
New task
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

<CreateChannelModal open={modalOpen} onOpenChange={setModalOpen} />
</>
);
}
Loading
Loading