Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ vi.mock("@posthog/ui/features/canvas/hooks/useChannelStars", () => ({
toggleStar: mocks.toggleStar,
}),
}));
vi.mock("@posthog/ui/features/canvas/hooks/usePersonalSpaceName", () => ({
usePersonalSpaceName: () => "me",
useSpaceDisplayName: (name: string | undefined) => name,
}));

import { useChannelPaneStore } from "@posthog/ui/features/canvas/stores/channelPaneStore";
import { ChannelBackRow } from "./ChannelBackRow";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useChannels,
} from "@posthog/ui/features/canvas/hooks/useChannels";
import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout";
import { useSpaceDisplayName } from "@posthog/ui/features/canvas/hooks/usePersonalSpaceName";
import { PERSONAL_CHANNEL_NAME } from "@posthog/ui/features/canvas/hooks/useTaskChannels";
import { showChannelList } from "@posthog/ui/features/canvas/stores/channelPaneStore";
import { track } from "@posthog/ui/shell/analytics";
Expand Down Expand Up @@ -55,6 +56,9 @@ export function ChannelBackRow({ channelId }: { channelId: string }) {
const spacesLayout = useChannelsLayout();
const { channels, isLoading } = useChannels();
const current = channels.find((c) => c.id === channelId);
// Display only: the personal space reads as the user's name (the star/glyph
// logic below still keys off the raw "me" name).
const currentLabel = useSpaceDisplayName(current?.name);
const showStar = current != null && current.name !== PERSONAL_CHANNEL_NAME;
const glyph = channelGlyph(current?.name, {
size: 14,
Expand Down Expand Up @@ -101,7 +105,7 @@ export function ChannelBackRow({ channelId }: { channelId: string }) {
)}
<span className="min-w-0 flex-1 truncate font-semibold text-[13px] text-foreground">
{current ? (
current.name
currentLabel
) : isLoading ? (
// A placeholder word here would read as a real channel named
// "channel"; a skeleton says "still loading" honestly.
Expand Down
13 changes: 11 additions & 2 deletions packages/ui/src/features/canvas/components/ChannelBreadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ import { useNavigate, useRouterState } from "@tanstack/react-router";
import { type ReactNode, useState } from "react";

interface ChannelBreadcrumbProps {
/** The channel (root) segment label. */
/**
* The channel's raw name, used for the root segment's glyph (e.g. the "me"
* space's lock). Also the default label unless `channelLabel` overrides it.
*/
channelName: string;
/**
* What the root segment reads as, when it differs from the raw name — the
* personal space shows the user's name but keeps "me" for its glyph/identity.
*/
channelLabel?: string;
/**
* When provided, the "# channel" segment links to the channel home, like the
* sidebar channel row and the channel-view header.
Expand Down Expand Up @@ -50,6 +58,7 @@ interface ChannelBreadcrumbProps {
// the channel home.
export function ChannelBreadcrumb({
channelName,
channelLabel,
channelId,
middle,
leafIcon,
Expand Down Expand Up @@ -80,7 +89,7 @@ export function ChannelBreadcrumb({
space: spacesLayout,
className: "shrink-0 text-muted-foreground/80",
})}
label={channelName}
label={channelLabel ?? channelName}
strong
// Nowhere to go from the space's own index, and no channelId means no
// route at all — either way the segment stops responding.
Expand Down
5 changes: 5 additions & 0 deletions packages/ui/src/features/canvas/components/ChannelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { useChannels } from "@posthog/ui/features/canvas/hooks/useChannels";
import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout";
import { useMarkChannelSeen } from "@posthog/ui/features/canvas/hooks/useMarkChannelSeen";
import { useSpaceDisplayName } from "@posthog/ui/features/canvas/hooks/usePersonalSpaceName";
import { Text } from "@radix-ui/themes";
import { useNavigate, useRouterState } from "@tanstack/react-router";

Expand All @@ -34,6 +35,9 @@ export function ChannelHeader({
const channelsLayout = useChannelsLayout();
const { channels } = useChannels();
const channelName = channels.find((c) => c.id === channelId)?.name;
// Display only — the personal space reads as the user's name, every other
// space as itself. `channelName` stays the raw name for identity below.
const displayName = useSpaceDisplayName(channelName);
// Every channel surface renders this header, so mark the channel read here.
useMarkChannelSeen(channelName);

Expand All @@ -45,6 +49,7 @@ export function ChannelHeader({
return (
<ChannelBreadcrumb
channelName={channelName ?? "Space"}
channelLabel={displayName ?? "Space"}
channelId={channelId}
leafIcon={page ? channelPageIcon(page, { size: 12 }) : undefined}
leafLabel={page ? channelPageLabel(page) : undefined}
Expand Down
18 changes: 18 additions & 0 deletions packages/ui/src/features/canvas/components/ChannelsList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const mocks = vi.hoisted(() => ({
starredPaths: [] as string[],
channelsLayout: true,
navigate: vi.fn(),
// The personal row's display label; "me" until a signed-in user resolves.
personalName: "me",
}));

vi.mock("@posthog/ui/shell/analytics", () => ({ track: vi.fn() }));
Expand Down Expand Up @@ -40,6 +42,11 @@ vi.mock("@posthog/ui/features/canvas/hooks/useTaskChannels", async () => {
>("@posthog/ui/features/canvas/hooks/useTaskChannels");
return { ...actual, useTaskChannels: () => ({ channels: [] }) };
});
vi.mock("@posthog/ui/features/canvas/hooks/usePersonalSpaceName", () => ({
usePersonalSpaceName: () => mocks.personalName,
useSpaceDisplayName: (name: string | undefined) =>
name === "me" ? mocks.personalName : name,
}));
vi.mock("@posthog/ui/features/canvas/components/RenameChannelModal", () => ({
RenameChannelModal: () => null,
}));
Expand Down Expand Up @@ -73,6 +80,7 @@ describe("ChannelsList", () => {
mocks.channels = [ME, ENG, DESIGN];
mocks.starredPaths = [];
mocks.channelsLayout = true;
mocks.personalName = "me";
// The pane store is module state: reset to its resting value so a test that
// slides the slider can't hand the next one a pre-focused search box.
showChannelPane();
Expand All @@ -93,6 +101,16 @@ describe("ChannelsList", () => {
expect(me.parentElement?.textContent).toMatch(/me(⌘|Ctrl)/);
});

it("shows the personal row as the user's name with a 'your space' tag", () => {
mocks.personalName = "Shy";
renderList();
// The name replaces the raw "me", and the quiet tag keeps it legible as the
// personal space (like Slack's "you" next to your own DM).
expect(screen.getByText("Shy")).toBeTruthy();
expect(screen.getByText("your space")).toBeTruthy();
expect(screen.queryByText("me")).toBeNull();
});

// "Starred" and "Spaces" are headings over the rows. Spaces receive a small
// Slack-style inset; the alpha keeps its deeper tree indentation.
describe("group headings", () => {
Expand Down
19 changes: 17 additions & 2 deletions packages/ui/src/features/canvas/components/ChannelsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
} from "@posthog/ui/features/canvas/hooks/useChannels";
import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout";
import { useCreateAndOpenDashboard } from "@posthog/ui/features/canvas/hooks/useDashboards";
import { usePersonalSpaceName } from "@posthog/ui/features/canvas/hooks/usePersonalSpaceName";
import { useStarredChannelSlots } from "@posthog/ui/features/canvas/hooks/useStarredChannelSlots";
import {
PERSONAL_CHANNEL_NAME,
Expand Down Expand Up @@ -697,6 +698,10 @@ function PersonalChannelRow({ hotkeySlot }: { hotkeySlot?: number }) {
// The "+" dropdown (New task / New canvas), mirroring a shared channel row.
const [newMenuOpen, setNewMenuOpen] = useState(false);
const isUnread = useIsChannelUnread()(PERSONAL_CHANNEL_NAME);
// Shown as the user's own name, so it's clear these are their tasks. The
// channel is still "me" for every identity/routing purpose (below).
const personalName = usePersonalSpaceName();
const hasResolvedName = personalName !== PERSONAL_CHANNEL_NAME;

const meFolder = channels.find((c) => c.name === PERSONAL_CHANNEL_NAME);
const createAndOpenCanvas = useCreateAndOpenDashboard(meFolder?.id);
Expand Down Expand Up @@ -759,8 +764,15 @@ function PersonalChannelRow({ hotkeySlot }: { hotkeySlot?: number }) {
: "text-muted-foreground group-hover/button:text-foreground",
)}
>
{PERSONAL_CHANNEL_NAME}
{personalName}
</span>
{hasResolvedName && (
// A quiet "your space" tag, like Slack's "you" next to your own DM,
// so the renamed row still reads as the personal space.
<span className="shrink-0 text-[11px] text-muted-foreground/60">
your space
</span>
)}
{hotkeySlot != null && (
<Kbd className="!mr-0 ml-auto shrink-0 opacity-50 group-hover/chan:opacity-0">
{formatHotkey(`mod+${hotkeySlot}`)}
Expand Down Expand Up @@ -919,6 +931,7 @@ export function ChannelsList() {
const channelsLayout = useChannelsLayout();

const isUnread = useIsChannelUnread();
const personalName = usePersonalSpaceName();

const [query, setQuery] = useState("");
const normalizedQuery = channelsLayout ? query.trim().toLowerCase() : "";
Expand All @@ -935,7 +948,9 @@ export function ChannelsList() {
// stand between you and the row you already named, and an empty "Starred"
// heading reads as a result that isn't there.
const searchResults = channels.filter((c) => matches(c.name));
const meMatches = matches(PERSONAL_CHANNEL_NAME);
// The personal row now reads as the user's name, so search it by that too —
// otherwise typing your own name wouldn't surface your own space.
const meMatches = matches(PERSONAL_CHANNEL_NAME) || matches(personalName);
const noMatches =
normalizedQuery !== "" && !meMatches && !searchResults.length;

Expand Down
33 changes: 33 additions & 0 deletions packages/ui/src/features/canvas/hooks/usePersonalSpaceName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useMeQuery } from "@posthog/ui/features/auth/useMeQuery";
import { PERSONAL_CHANNEL_NAME } from "@posthog/ui/features/canvas/hooks/useTaskChannels";

/**
* Display label for the personal ("me") space. We show the signed-in user's
* own name so it's obvious these are *their* tasks rather than a space literally
* called "me", which readers kept misreading.
*
* This only changes what's rendered — the channel is still identified, routed,
* matched, and provisioned under {@link PERSONAL_CHANNEL_NAME} everywhere. Falls
* back to that raw name until the (cached, fast) user record loads, or if the
* user has no name set.
*/
export function usePersonalSpaceName(): string {
const { data: user } = useMeQuery();
const name = [user?.first_name, user?.last_name]
.map((part) => part?.trim())
.filter(Boolean)
.join(" ");
return name || PERSONAL_CHANNEL_NAME;
}

/**
* Maps a raw channel name to what should be shown for it, swapping the personal
* channel's "me" for the user's name (see {@link usePersonalSpaceName}) and
* leaving every other space's name untouched.
*/
export function useSpaceDisplayName(
rawName: string | undefined,
): string | undefined {
const personalName = usePersonalSpaceName();
return rawName === PERSONAL_CHANNEL_NAME ? personalName : rawName;
}
Loading