Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 943ba3b

Browse files
authored
fix(spaces): rebrand section and row icons (#3871)
1 parent 659c5be commit 943ba3b

14 files changed

Lines changed: 84 additions & 31 deletions

packages/ui/src/features/browser-tabs/BrowserTabStrip.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
BrainIcon,
3-
HashIcon,
43
PlugsConnectedIcon,
54
RobotIcon,
65
SquaresFourIcon,
@@ -24,6 +23,7 @@ import {
2423
} from "@posthog/shared";
2524
import { channelSectionFor } from "@posthog/ui/features/canvas/channelSections";
2625
import { iconForTemplate } from "@posthog/ui/features/canvas/components/canvasTemplateIcon";
26+
import { channelGlyph } from "@posthog/ui/features/canvas/components/channelGlyph";
2727
import { ensurePersonalChannel } from "@posthog/ui/features/canvas/ensurePersonalChannel";
2828
import {
2929
useChannelMutations,
@@ -505,15 +505,18 @@ export function BrowserTabStrip() {
505505
};
506506
}
507507
// A channel tab: a sub-section (Artifacts/Recents/…) or the channel home.
508-
// The section drives the label; the channel name carries the `#` hover
508+
// The section drives the label; the channel name carries the space
509509
// context. Home has no section, so it labels by the channel name.
510510
if (channelId) {
511511
const meta = channelSectionFor(section);
512512
return {
513513
id: t.id,
514514
label:
515515
meta?.label ?? channel ?? (spacesLayout ? "Space" : "Channel"),
516-
icon: <HashIcon size={14} />,
516+
icon: channelGlyph(channel ?? undefined, {
517+
size: 14,
518+
space: spacesLayout,
519+
}),
517520
channelName: channel,
518521
// No section meta → the channel's index page.
519522
isChannelHome: !meta,

packages/ui/src/features/canvas/AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ The root `AGENTS.md` architecture rules still apply.
4747
slide and returning to the list doesn't rebuild every row. A two-finger
4848
horizontal swipe moves between them (`useChannelPaneSwipe`, wheel `deltaX`
4949
accumulated per gesture and locked until the wheel goes quiet).
50-
- In the list, "Starred"/"Channels" are headings, not parents: under the layout
51-
the rows sit at the heading's level (no indent) and the "#"/lock glyph belongs
52-
to the rows. The alpha's indented tree is unchanged.
50+
- In the list, "Starred"/"Spaces" are headings above lightly indented shared
51+
rows; the pinned private "me" row aligns with the headings. The alpha's more
52+
deeply indented Channels tree and hash glyphs are unchanged.
5353
- One `ChannelsFab` serves both panes: given a `channelId` it creates inside
5454
that channel (task, canvas), and either way it can create a channel. Off the
5555
layout it keeps its original two-item menu. Archived moves out of the sidebar

packages/ui/src/features/canvas/components/ChannelBackRow.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const mocks = vi.hoisted(() => ({
1010
}));
1111

1212
vi.mock("@posthog/ui/shell/analytics", () => ({ track: vi.fn() }));
13+
vi.mock("@posthog/ui/features/canvas/hooks/useChannelsLayout", () => ({
14+
useChannelsLayout: () => true,
15+
}));
1316
vi.mock("@posthog/ui/features/canvas/hooks/useChannels", () => ({
1417
useChannels: () => ({ channels: mocks.channels, isLoading: mocks.isLoading }),
1518
}));

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
type Channel,
88
useChannels,
99
} from "@posthog/ui/features/canvas/hooks/useChannels";
10+
import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout";
1011
import { PERSONAL_CHANNEL_NAME } from "@posthog/ui/features/canvas/hooks/useTaskChannels";
1112
import { showChannelList } from "@posthog/ui/features/canvas/stores/channelPaneStore";
1213
import { Tooltip } from "@posthog/ui/primitives/Tooltip";
@@ -45,6 +46,7 @@ function RowStar({ channel }: { channel: Channel }) {
4546
* it. Leaving the channel scoped means the route (and the main pane) stay put.
4647
*/
4748
export function ChannelBackRow({ channelId }: { channelId: string }) {
49+
const spacesLayout = useChannelsLayout();
4850
const { channels, isLoading } = useChannels();
4951
const current = channels.find((c) => c.id === channelId);
5052
const showStar = current != null && current.name !== PERSONAL_CHANNEL_NAME;
@@ -77,6 +79,7 @@ export function ChannelBackRow({ channelId }: { channelId: string }) {
7779
<span className="flex w-4 shrink-0 items-center justify-center">
7880
{channelGlyph(current?.name, {
7981
size: 14,
82+
space: spacesLayout,
8083
className: "text-muted-foreground",
8184
})}
8285
</span>

packages/ui/src/features/canvas/components/ChannelBreadcrumb.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { describe, expect, it, vi } from "vitest";
55
vi.mock("@tanstack/react-router", () => ({
66
useNavigate: () => vi.fn(),
77
}));
8+
vi.mock("@posthog/ui/features/canvas/hooks/useChannelsLayout", () => ({
9+
useChannelsLayout: () => true,
10+
}));
811

912
import { ChannelBreadcrumb } from "./ChannelBreadcrumb";
1013

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TooltipTrigger,
66
} from "@posthog/quill";
77
import { channelGlyph } from "@posthog/ui/features/canvas/components/channelGlyph";
8+
import { useChannelsLayout } from "@posthog/ui/features/canvas/hooks/useChannelsLayout";
89
import { HeaderTitleEditor } from "@posthog/ui/features/task-detail/HeaderTitleEditor";
910
import { Flex, Text } from "@radix-ui/themes";
1011
import { useNavigate } from "@tanstack/react-router";
@@ -45,6 +46,7 @@ export function ChannelBreadcrumb({
4546
onRename,
4647
trailing,
4748
}: ChannelBreadcrumbProps) {
49+
const spacesLayout = useChannelsLayout();
4850
const currentEditScope = editScopeKey ?? leafLabel;
4951
const [editingScope, setEditingScope] = useState<string | null>(null);
5052
const editing = editingScope === currentEditScope;
@@ -54,6 +56,7 @@ export function ChannelBreadcrumb({
5456
<>
5557
{channelGlyph(channelName, {
5658
size: 12,
59+
space: spacesLayout,
5760
className: "mt-px shrink-0 text-muted-foreground/80",
5861
})}
5962
<Text

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export function ChannelHeader({ channelId }: { channelId: string }) {
3333
>
3434
{channelGlyph(channelName, {
3535
size: 20,
36+
space: channelsLayout,
3637
className: "shrink-0 text-muted-foreground/80",
3738
})}
3839
<Text className="min-w-0 truncate font-medium" title={channelName}>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ChartBarIcon,
3+
CubeIcon,
34
FileTextIcon,
45
HashIcon,
56
PlusIcon,
@@ -70,7 +71,7 @@ export function ChannelsFab({ channelId }: { channelId?: string }) {
7071

7172
const newChannelItem = (
7273
<DropdownMenuItem onClick={() => setModalOpen(true)}>
73-
<HashIcon size={14} className="text-gray-9" />
74+
{channelsLayout ? <CubeIcon size={14} /> : <HashIcon size={14} />}
7475
{channelsLayout ? "New space" : "New channel"}
7576
</DropdownMenuItem>
7677
);

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,28 @@ describe("ChannelsList", () => {
9393
expect(me.parentElement?.textContent).toMatch(/me(|Ctrl)/);
9494
});
9595

96-
// "Starred" and "Channels" are headings over the rows, not parents of them —
97-
// under the layout the rows sit at the heading's level and keep the "#" for
98-
// themselves. The alpha's tree is unchanged.
96+
// "Starred" and "Spaces" are headings over the rows. Spaces receive a small
97+
// Slack-style inset; the alpha keeps its deeper tree indentation.
9998
describe("group headings", () => {
10099
beforeEach(() => {
101100
mocks.starredPaths = [ENG.path];
102101
});
103102

104-
it("does not indent rows under the layout", () => {
103+
it("slightly indents rows under the layout", () => {
105104
renderList();
106-
expect(screen.getByText("engineering").closest(".pl-5")).toBeNull();
105+
expect(screen.getByText("engineering").closest("button")).toHaveClass(
106+
"pl-4",
107+
);
108+
expect(screen.getByText("me").closest("button")).not.toHaveClass("pl-4");
107109
});
108110

109111
it("keeps the indented tree off the layout", () => {
110112
mocks.channelsLayout = false;
111113
renderList();
112114
expect(screen.getByText("engineering").closest(".pl-5")).toBeTruthy();
115+
expect(screen.getByText("engineering").closest("button")).not.toHaveClass(
116+
"pl-4",
117+
);
113118
});
114119

115120
it("rebrands only the spaces layout", () => {

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
CaretDownIcon,
44
CaretRightIcon,
55
ChartBarIcon,
6+
CubeFocusIcon,
67
DotsThreeIcon,
78
FileTextIcon,
89
HashIcon,
@@ -439,9 +440,11 @@ function ChannelSection({
439440
data-selected={isActive || undefined}
440441
onClick={() => openChannel(channel)}
441442
{...focusProps}
443+
className={spacesLayout ? "pl-4" : undefined}
442444
>
443445
{channelGlyph(channel.name, {
444446
size: 14,
447+
space: spacesLayout,
445448
weight: isUnread ? "bold" : undefined,
446449
className: cn(
447450
"shrink-0",
@@ -812,30 +815,30 @@ const CHANNELS_SECTION_ID = "channels:all";
812815
// the label styling) and animates the panel height (which janked on a list this
813816
// long). Unstyled parts give a plain label row that snaps.
814817
//
815-
// The whole header row is the trigger. Under the layout the icon well rests
816-
// empty and fills with a chevron on hover or keyboard focus, so the row only
817-
// advertises the disclosure when you're reaching for it — a "#" there read as a
818-
// channel named "Starred", and the glyph belongs to the rows, not the label
819-
// above them.
818+
// The whole header row is the trigger. Its section glyph swaps to a down/right
819+
// disclosure caret on hover or keyboard focus while keeping Starred and Spaces
820+
// distinct at rest.
820821
function ChannelGroup({
821822
sectionId,
822823
label,
823824
className,
824825
flat,
825826
keepMounted = true,
827+
icon,
826828
children,
827829
}: {
828830
sectionId: string;
829831
label: string;
830832
className?: string;
831-
/** Layout-only: rows sit at the label's level instead of indented under it. */
833+
/** Layout-only: removes the legacy tree indent; rows apply their own inset. */
832834
flat?: boolean;
833835
/**
834836
* Off under the layout: a kept-mounted collapsed row is still an Autocomplete
835837
* option, so ↓ would walk onto spaces the user has folded away. Paying the
836838
* rebuild on expand is better than highlighting a row nobody can see.
837839
*/
838840
keepMounted?: boolean;
841+
icon: ReactNode;
839842
children: ReactNode;
840843
}) {
841844
const collapsedSections = useSidebarStore((s) => s.collapsedSections);
@@ -860,12 +863,9 @@ function ChannelGroup({
860863
render={<MenuLabel render={<button type="button" />} />}
861864
>
862865
<span className="relative flex size-3.5 shrink-0 items-center justify-center">
863-
{!flat && (
864-
<HashIcon
865-
size={14}
866-
className="group-hover/group-trigger:hidden group-focus-visible/group-trigger:hidden"
867-
/>
868-
)}
866+
<span className="group-hover/group-trigger:hidden group-focus-visible/group-trigger:hidden">
867+
{icon}
868+
</span>
869869
{isOpen ? (
870870
<CaretDownIcon
871871
size={14}
@@ -1006,6 +1006,7 @@ export function ChannelsList() {
10061006
label="Starred"
10071007
flat={channelsLayout}
10081008
keepMounted={!channelsLayout}
1009+
icon={<StarIcon size={14} />}
10091010
>
10101011
{starred.map((channel) => (
10111012
<ChannelSection
@@ -1023,6 +1024,9 @@ export function ChannelsList() {
10231024
label={channelsLayout ? "Spaces" : "Channels"}
10241025
flat={channelsLayout}
10251026
keepMounted={!channelsLayout}
1027+
icon={
1028+
channelsLayout ? <CubeFocusIcon size={14} /> : <HashIcon size={14} />
1029+
}
10261030
>
10271031
{!isLoading && channels.length === 0 && (
10281032
<Empty className="px-2 py-1 text-subtle-foreground text-xs">

0 commit comments

Comments
 (0)