diff --git a/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx b/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx
index 705d724870..c572ba1cda 100644
--- a/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx
+++ b/packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx
@@ -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";
diff --git a/packages/ui/src/features/canvas/components/ChannelBackRow.tsx b/packages/ui/src/features/canvas/components/ChannelBackRow.tsx
index 0e91183d06..05ff75a571 100644
--- a/packages/ui/src/features/canvas/components/ChannelBackRow.tsx
+++ b/packages/ui/src/features/canvas/components/ChannelBackRow.tsx
@@ -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";
@@ -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,
@@ -101,7 +105,7 @@ export function ChannelBackRow({ channelId }: { channelId: string }) {
)}
{current ? (
- current.name
+ currentLabel
) : isLoading ? (
// A placeholder word here would read as a real channel named
// "channel"; a skeleton says "still loading" honestly.
diff --git a/packages/ui/src/features/canvas/components/ChannelBreadcrumb.tsx b/packages/ui/src/features/canvas/components/ChannelBreadcrumb.tsx
index 670f071518..1e58d73540 100644
--- a/packages/ui/src/features/canvas/components/ChannelBreadcrumb.tsx
+++ b/packages/ui/src/features/canvas/components/ChannelBreadcrumb.tsx
@@ -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.
@@ -50,6 +58,7 @@ interface ChannelBreadcrumbProps {
// the channel home.
export function ChannelBreadcrumb({
channelName,
+ channelLabel,
channelId,
middle,
leafIcon,
@@ -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.
diff --git a/packages/ui/src/features/canvas/components/ChannelHeader.tsx b/packages/ui/src/features/canvas/components/ChannelHeader.tsx
index 40f65fb29e..1e624c8aa5 100644
--- a/packages/ui/src/features/canvas/components/ChannelHeader.tsx
+++ b/packages/ui/src/features/canvas/components/ChannelHeader.tsx
@@ -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";
@@ -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);
@@ -45,6 +49,7 @@ export function ChannelHeader({
return (
({
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() }));
@@ -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,
}));
@@ -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();
@@ -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", () => {
diff --git a/packages/ui/src/features/canvas/components/ChannelsList.tsx b/packages/ui/src/features/canvas/components/ChannelsList.tsx
index 1b648d1959..aa3a6c3da9 100644
--- a/packages/ui/src/features/canvas/components/ChannelsList.tsx
+++ b/packages/ui/src/features/canvas/components/ChannelsList.tsx
@@ -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,
@@ -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);
@@ -759,8 +764,15 @@ function PersonalChannelRow({ hotkeySlot }: { hotkeySlot?: number }) {
: "text-muted-foreground group-hover/button:text-foreground",
)}
>
- {PERSONAL_CHANNEL_NAME}
+ {personalName}
+ {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.
+
+ your space
+
+ )}
{hotkeySlot != null && (
{formatHotkey(`mod+${hotkeySlot}`)}
@@ -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() : "";
@@ -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;
diff --git a/packages/ui/src/features/canvas/hooks/usePersonalSpaceName.ts b/packages/ui/src/features/canvas/hooks/usePersonalSpaceName.ts
new file mode 100644
index 0000000000..23adb37599
--- /dev/null
+++ b/packages/ui/src/features/canvas/hooks/usePersonalSpaceName.ts
@@ -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;
+}