Skip to content

feat(channels): collapsible sidebar groups, # icons, two-step create, context→channel rename#3510

Merged
trunk-io[bot] merged 5 commits into
mainfrom
posthog-code/channels-sidebar-and-create-flow
Jul 16, 2026
Merged

feat(channels): collapsible sidebar groups, # icons, two-step create, context→channel rename#3510
trunk-io[bot] merged 5 commits into
mainfrom
posthog-code/channels-sidebar-and-create-flow

Conversation

@adamleithp

@adamleithp adamleithp commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Continues the channels (Project Bluebird) surface work. Grouped by area:

2026-07-16 14 39 42

Rename: context → channel

  • User-facing copy only — no types, no analytics wire strings, no backend fields.
  • Sidebar heading, empty state, star/rename/delete labels, delete-confirm dialog and its toasts.
  • Channel intro card: "created this channel", "beginning of the X channel", "Learn more about channels".
  • Create/rename modals, activity view, command palette group, sidebar nav row, browser-tab fallback label, channel header fallback.
  • contextPrompt.ts — the agent prompt now says Build a CONTEXT.md for the channel "X" (was "context/folder"). This was the highest-leverage one: it was teaching the model the stale noun, seeding it into every generated CONTEXT.md.
  • Deliberately untouched: context.md itself, ANALYTICS_EVENTS.CONTEXT_ACTION / context_md_building / context_name (renaming would split existing PostHog insights and break old feed rows), and identifiers like channelContext / ContextMdState that mean the file, not the channel.

Sidebar: collapsible groups

  • "Starred" and "Channels" are now collapsible; the whole header row is the trigger.
  • Rests as a #, swaps to a chevron on hover/focus.
  • Collapse state persists per section in the shared sidebar store, keyed channels:* to stay clear of the Code sidebar's folder keys.
  • Uses Base UI's Collapsible directly, not quill's: quill styles its trigger as a button (which fought the MenuLabel styling these headers had) and animates panel height, which janked against a long list.
  • Panel is keepMounted. Each row builds a context menu, dropdown, tooltip and two dialogs up front, so the default unmount-on-close made every expand rebuild the lot — ~940ms → ~75ms for 46 channels (collapsing was always ~80ms).

Icons

  • Every remaining SquircleDashed# (phosphor HashIcon), across channel rows, #me, group headers, channel header, breadcrumb, rename field, command palette and browser tabs. lucide-react dropped from those files.

Create flow

  • Floating + button bottom-right of the channel list (tooltip "Create something new"), menu opens upward with New channel / New task. Replaces the small + beside the "Channels" label.
  • + added to the #me row, matching the other channels. The me folder is created lazily, so its actions resolve the id at click time rather than closing over it — hence the optional call-time channelId override on useCreateAndOpenDashboard (without it, "New canvas" on a never-opened #me silently no-ops against a stale undefined).
  • Channel creation split into two steps: name → Next, then "What's this channel about?" → Skip / Create.
  • Step two is a real nested dialog, so quill scales and dims step one behind it — the stack itself is the affordance that says there's another step. Escape returns to the name, intact. The nesting offset is inverted from quill's default so step one peeks above rather than below.
  • Nothing is created until step two resolves: Create = channel + CONTEXT.md plan session; Skip = channel alone (its intro card still offers CONTEXT.md later).

Fix: empty-state flash on channel creation

  • Creating a channel flashed the old welcome + suggestions screen before the feed appeared.
  • WebsiteChannelHome took only channel from useBackendChannel and dropped its isLoading. A new channel resolves its backend channel over the network (a resolve-or-create POST); for that whole window there was no intro (it needs backendChannel), no tasks, and — because the feed query is disabled until the id exists, and a disabled react-query reports isLoading: false — the view believed it wasn't loading. So ChannelFeedView fell through to emptyState.
  • Fix folds the identity resolution into the loading flag: a channel isn't empty until we know which channel it is.
  • A plain reload never showed this, because the backend channels list is already cached by the time the view mounts.

Testing

  • Typecheck, biome, and the canvas/browser-tabs/command test suites pass.
  • Driven against the real app over CDP: FAB menu and both items, the two-step dialog (including measuring that step one sits above step two), the #→chevron hover swap, collapse/expand persistence, and the #me +.
  • The flash fix was verified by creating a channel and sampling the visible view every 25ms: now spinner → intro, no empty state at any point. Two throwaway probe channels were created and deleted.

Known gaps

  • Sidebar interactions still cost ~650ms+ in dev regardless of this change — opening the FAB alone (local state, no collapsible) measures ~660ms, and the app does ~13.5k re-renders during 3s idle. The whole tree re-renders on any state change and repays 46 expensive rows. Pre-existing and out of scope here; the real fix is building each row's menus/dialogs lazily.
  • If the resolve-or-create POST fails permanently you now get a spinner where you previously got the (misleading) suggestions screen. useBackendChannel retries on failure, so it spins rather than settles.

🤖 Generated with Claude Code

https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj

@trunk-io

trunk-io Bot commented Jul 16, 2026

Copy link
Copy Markdown

😎 Merged directly without going through the merge queue, as the queue was empty and the PR was up to date with the target branch - details.

adamleithp and others added 3 commits July 16, 2026 14:40
Group the channel sidebar under collapsible "Starred"/"Channels" headers.
The whole header row is the trigger; it rests as a "#" and swaps to a
chevron on hover/focus. Collapse state persists per section in the shared
sidebar store, keyed `channels:*` to stay clear of the Code sidebar's
folder keys.

Uses Base UI's Collapsible directly rather than quill's: quill styles its
trigger as a button (which fought the MenuLabel styling the headers had)
and animates panel height, which janked against a list this long. The
panel is `keepMounted` — every row builds a context menu, dropdown,
tooltip and two dialogs up front, so unmounting on close made each expand
rebuild the lot (~940ms for 46 channels, vs ~80ms to collapse).

Replace every remaining SquircleDashed with a "#", so channel rows, group
headers, tabs, header, breadcrumb, rename field and command palette all
speak one icon.

Split channel creation into two steps: name (CTA "Next"), then "what's
this channel about?" with Skip/Create. Step two is a real nested dialog,
so quill scales and dims step one behind it and the stack itself says
there's another step; escaping step two returns to the name, intact.
Nothing is created until Create (channel + CONTEXT.md plan session) or
Skip (channel alone). The nesting offset is inverted from quill's default
so step one peeks above rather than below.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
WebsiteChannelHome took only `channel` from useBackendChannel and dropped
its isLoading. For a freshly created channel that hook resolves the
backend channel over the network (a resolve-or-create POST), and for that
whole window the view had no intro (it needs backendChannel), no tasks,
and — because the feed query is disabled until the id exists, and a
disabled query reports isLoading:false — believed it wasn't loading. So
ChannelFeedView fell through to `emptyState` and flashed the welcome +
suggestions screen before the feed appeared.

Fold the identity resolution into the loading flag: a channel isn't empty
until we know which channel it is. A plain reload never showed this
because the backend channels list is already cached by the time the view
mounts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

React Doctor found 2 issues in 2 files · 2 warnings.

2 warnings

src/features/canvas/components/ChannelsList.tsx

src/features/canvas/components/CreateChannelModal.tsx

Reviewed by React Doctor for commit df475a8.

@adamleithp
adamleithp force-pushed the posthog-code/channels-sidebar-and-create-flow branch from be4b655 to 8ce968a Compare July 16, 2026 13:41
@adamleithp adamleithp added the Stamphog This will request an autostamp by stamphog on small changes label Jul 16, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a 777-line feature PR with zero reviews touching meaningful UX behavior (two-step channel-create modal, collapsible sidebar groups, new FAB component, pervasive rename). Zero reviews on a change of this size and scope warrants human sign-off. There is also a visible copy error in the empty state: "No channels other channels yet." needs fixing before ship.

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 16, 2026
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(channels): don't flash the empty sta..." | Re-trigger Greptile

Comment thread packages/ui/src/features/canvas/components/WebsiteChannelHome.tsx Outdated
Comment thread packages/ui/src/features/canvas/components/ChannelsList.tsx Outdated
adamleithp and others added 2 commits July 16, 2026 15:11
Addresses review on #3510.

- Empty state read "No channels other channels yet."; it's the all-channels
  empty case, so "No channels yet."

- A failed backend-channel resolve left the feed spinning forever: the
  view waited on `!backendChannel`, which never clears. Worse, the resolve
  effect re-fired on its own settle (isResolving true→false with existing
  still undefined), so a failure looped the POST indefinitely. Guard the
  effect on failure and expose `resolveFailed` so the view stops waiting.
  Scoped to the name that failed (the mutation's variables), so switching
  to another channel still gets its own attempt.

- Personal-channel provisioning could double-create: the sidebar row and
  the new-tab path each guarded their own in-flight create, which still
  raced each other, and folder creation isn't idempotent by path. One
  shared ensurePersonalChannel now serves both.

Not changed: React Doctor's `control-has-associated-label` on the group
trigger is a false positive — it has a text label, and the a11y tree reads
it as button "CHANNELS".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019G63f4afY9vsbKsvK654Wj
…solve window

Move the identity-resolution loading window into useBackendChannel itself:
the hook previously reported isLoading:false in the gap between the channel
list arriving and the resolve effect firing, which callers had to paper over
(WebsiteChannelHome's `!!channelName && !backendChannel && !resolveFailed`
term). The hook now stays loading until a channel resolves or the resolve
fails, so the call-site composition collapses to its three real sources and
the resolveFailed return field is no longer needed.

Adds red-green tests covering the no-flash window, settling (without a hot
retry loop) on a failed resolve, re-resolving a new name after a failure,
and the personal-channel mapping.

Generated-By: PostHog Code
Task-Id: 85ec2ee5-76f5-472c-9687-9958a396e142
@adamleithp
adamleithp enabled auto-merge (squash) July 16, 2026 14:21
@trunk-io
trunk-io Bot merged commit 6f243a0 into main Jul 16, 2026
31 checks passed
@trunk-io
trunk-io Bot deleted the posthog-code/channels-sidebar-and-create-flow branch July 16, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants